// Copyright (c) 2012 by BLUEnLIVE (http://TEUS.me)
// 
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files
// (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software,
// and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT
// OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
// OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/////////////////////////////////////////////////////////////////
//
// zen_coding_v4.cpp
//
// coded based on STL (wstring and vector)
// converted from Zen.Coding-textarea.v0.7.1.zip
//   developed by Sergey Chikuyonok (serge.che@gmail.com)
// converted by BLUEnLIVE from April-05-2012 till June-19-2012
//
/////////////////////////////////////////////////////////////////


////////////////////////////
// 1st block              //
//        : #include, etc //
////////////////////////////


#include "zen_coding_v4.h"
#include <vector>
#include <string>
#include <sstream>

using namespace std;

#ifndef ZENCODINGLANG
#define ZENCODINGLANG	L"en"
#endif
#ifndef ZENCODINGLOCALE
#define ZENCODINGLOCALE	L"en-US"
#endif
#ifndef ZENCODINGCHRSET
#define ZENCODINGCHRSET	L"UTF-8"
#endif


////////////////////////////
// 2nd block              //
//         : Declaration  //
////////////////////////////


// global setting
////////////////////////////////////////////////////////////

class CGlobalSetting {
public:
	CGlobalSetting();
	virtual ~CGlobalSetting();
	void setCaretPlaceholder(wstring newcaretplaceholder) { caret_placeholder = newcaretplaceholder; }
	wstring getCaretPlaceholder() { return caret_placeholder; }

protected:
	wstring caret_placeholder;
};

CGlobalSetting global;


// declaration of functions for element data types
////////////////////////////////////////////////////////////

#define ISEOL(ch) (((ch)==0)||((ch)==L'\r')||((ch)==L'\n'))
#define ISNUMERIC(ch) (((ch)>=L'0')&&((ch)<=L'9'))
#define ISUPPERCASE(ch) (((ch)>=L'A')&&((ch)<=L'Z'))
#define ISLOWERCASE(ch) (((ch)>=L'a')&&((ch)<=L'z'))
#define ISWORD(ch) ((((ch)>=L'A')&&((ch)<=L'Z'))||(((ch)>=L'a')&&((ch)<=L'z'))||(((ch)>=L'0')&&((ch)<=L'9'))||((ch)==L'_'))
#define ISSPACE(ch) (((ch)==L' ')||((ch)==L'\t'))
#define ISCRORLF(ch) (((ch)==L'\r')||((ch)==L'\n'))


// additional element data types
////////////////////////////////////////////////////////////

typedef struct _zen_abbrev_list
{
	wchar_t *pabbr;
	wchar_t *pexpa;
} ZENABBREVLIST, *PZENABBREVLIST;


class CIntStack {
public:
	CIntStack();
	virtual ~CIntStack();

	void push(int value);
	bool pop(int &value);

protected:
	vector <int> ints;
};


class CWstrsStack {
public:
	CWstrsStack();
	virtual ~CWstrsStack();

	void push(wstring &str1, wstring &str2);
	void push(wchar_t *str1, wchar_t *str2);
	void pushWithCheck(wstring &str1, wstring &str2);
	void pushWithCheck(wchar_t *str1, wchar_t *str2);
	bool pop(wstring &str1, wstring &str2);
	void removeAll();
	int getLen() { return (int)strs1.size(); }
	bool getVal(int iPos, wstring &str1, wstring &str2);
	void setVal(int iPos, wstring str2);
	void appendStack(CWstrsStack *source);
	void appendStack(CWstrsStack &source);
	int iFind(wstring &str1, wstring &str2);
	void pushAsAttribute(wstring name, wstring value);

protected:
	vector <wstring> strs1;
	vector <wstring> strs2;
};


// RegExps
////////////////////////////////////////////////////////////

/** Regular expression for setAbbreviation() */
inline int iRegExp00(wstring &str, int iBegin=0);	// /\*(\d+)?$/

/** Regular expression for getWord() */
inline int iRegExp01(wstring &str, int iBegin=0);	// /^[\w\-:\$]+/

/** Regular expression for extractAttributes() */
inline int iRegExp02(wstring &str);	// /^(["'])((?:(?!\1)[^\\]|\\.)*)\1/

/** Regular expression for extractAttributes() */
inline int iRegExp03(wstring &str, int iBegin=0);	// /(.+?)(\s|$)/

/** zen_parser.re_valid_name */
inline int iRegExp04(wstring &str);	// /^[\w\d\-_\$\:@!]+\+?$/i

/** Regular expression for XML tag matching */
int iRegExp05Sub(wstring &str, int iBegin=0);	// (?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)
inline int iRegExp05(wstring &str, wstring &m1, wstring &m2, wstring &m3);	// /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/
inline int iRegExp06(wstring &str, wstring &m1, wstring &m2, int iBegin=0);	// /([\w\-]+)\s*=\s*(['"])(.*?)\2/g

/** Removes any unnecessary characters from node name */
inline int iRegExp07(wstring str);	// /(.+)\!$/
wstring filterNodeName(wstring str);

/** zen_coding.re_tag */
int iRegExp08Sub1(wstring &str, int iBegin=0); // (?:"[^"]*")
int iRegExp08Sub2(wstring &str, int iBegin=0); // (?:'[^']*')
int iRegExp08Sub3(wstring &str, int iBegin=0); // [^>\s]+
int iRegExp08Sub4(wstring &str, int iBegin=0); // (?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))
int iRegExp08Sub5(wstring &str, int iBegin=0); // (?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)
inline int iRegExp08(wstring &str, int iBegin=0); // /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/

/** used in zen_coding.parseIntoTree
 *  remove filters from abbreviation
 */
inline int iRegExp09(wstring &str, wstring &ret, int iBegin=0); // /\|([\w\|\-]+)$/
void moveFilterFromAbbr(wstring &abbr, wstring &filter);

// filter: fc
inline int iRegExp10(wstring str, int &iFilteredLen, int iBegin=0); // /([\w\-]+\s*:)(?!:)\s*/

// used in replaceVariables()
inline int iRegExp11(wstring &str, wstring &variable, int iBegin=0); // /\$\{([\w\-]+)\}/

// used in runFilter_t()
inline int iRegExp12(wstring &str, int &begintoremove); // /^([\s|\u00a0])?[\d|#|\-|\*|\u2022]+\.?\s*/

// used in runFilter_xsl()
inline int iRegExp13(wstring str, int iBegin=0); // /\s+select\s*=\s*(['"]).*?\1/


// functions for wstring
////////////////////////////////////////////////////////////

inline int parseInt(wstring &str, int begin=0, int len=-1);
inline bool compareWstr(wstring &origin, wstring compareto, int begin=0);

// converted in C++ style. passing function pointers removed and style changed.
namespace ZenReplaceMode {
	enum Enum {
		toString, pasteContent, replaceCounter
	};
}
wstring replaceCounter(wstring str, int value=0);
wstring replaceUnescapedSymbol(wstring str, wstring symbol, wstring text, ZenReplaceMode::Enum replacemode=ZenReplaceMode::toString, int value=0);

wstring zeroPadString(wstring str, int pad);
wstring intToWstr(int n);

wstring escapeText(wstring text);
wstring unescapeText(wstring text);
wstring escapeChars(wstring text);
wstring repeatString(wstring str, int how_many);
void splitWstr(wstring str, wstring seperator, vector <wstring> &result);
wstring joinWstr(vector <wstring> &array, wchar_t *seperator=NULL);
bool compareWstrSub(wstring &src, int pos, wstring &tocompare, bool caseinsensitive);
wstring replaceWstr(wstring src, wstring tofind, wstring toreplace, bool global=false, bool caseinsensitive=false);

// in ogirinal js source, only 1 replaceVariables() exists
// but, in C++ version, splitted to 2 position.
// this is used with getVariable() and vars==NULL
wstring replaceVariables(wstring src);
void makeUpperWstr(wstring &text);
void makeLowerWstr(wstring &text);

wstring ltrimWstr(wstring str);
wstring removenewlines(wstring str);
void subwstr(wstring &str, int begin, int len=-1);
wstring trimWstr(wstring str);
bool findNewLine(wstring &str, int begin, int &pos, int &len);
void replaceTab2DblSpace(wstring &str);
void replace2CRLF(wstring &str);


// data types for Zen-coding
////////////////////////////////////////////////////////////

// syntax: html, css, xml, xsl
// profile: xthml, xml
// name: abbreviation, snippet
// type: abbreviation, expando, reference
//
// syntax, profile are defined in zen_coding_v4.h

namespace ZenName {
	enum Enum {
		abbreviation, snippet
	};
}

namespace ZenType {
	enum Enum {
		undefined, abbreviation, expando,
		reference, valueonly, tag
	};
}

namespace ZenFilters {
	enum Enum {
		c, css, e, fc, _format,
		haml, html, s, t, xsl
	};
}


class CParsedItem {
public:
	CParsedItem();
	virtual ~CParsedItem();
	void cleanUp();
	void setType(ZenType::Enum type);
	void setTypeKeyValue(ZenType::Enum type, wstring key, wstring value);
	void setNameIsempty(wstring name, bool is_empty);
	void setValueOnly(wstring value);
	void appendAttribute(wstring &attrs);
	void getValues(ZenType::Enum &type, wstring &key, wstring &value, wstring &name, bool &is_empty);
	CWstrsStack *getAttributes();
	void copyFrom(CParsedItem *oldone);

	void entry(ZenType::Enum type, wstring &key, wstring &value);
	void makeExpando(wstring &key, wstring &value);
	void makeAbbreviation(wstring &key, wstring &tag_name, wstring &attrs, bool is_empty);
	void parseAbbreviation(wstring key, wstring value);

	// for safe read/write
	wstring getValue() { return this->value; }
	wstring getName() { return this->name; }
	int getType() { return this->type; }
	bool getIsEmpty() { return this->is_empty; }

protected:
	ZenType::Enum type;
	wstring key;
	wstring value;
	wstring name;
	bool is_empty;
	CWstrsStack attributes;
};


class CTreeNode {
public:
	CTreeNode();
	virtual ~CTreeNode();

	CTreeNode *getParent() { return this->parent; }
	CTreeNode *addChild(CTreeNode *newchild);
	void replace(CTreeNode *newNode);
	bool setAbbreviation(wstring abbr);
	CTreeNode *copy();
	bool isEmpty() { return !(this->abbrivation.size()); }
	bool isTextNode() { return !(this->name.size()) && (this->text.size()); }
	bool hasEmptyChildren();
	int getChildCount() { return (int)(this->children.size()); }
	void setNstChild(int iPos, CTreeNode *newchild) {
		if ((!newchild) || (iPos<0) || ((int)(this->children.size())<=iPos)) return;
		if (this->children[iPos])
			delete this->children[iPos];
		this->children[iPos] = newchild;
	}
	CTreeNode *getNstChild(int iPos);
	void insert1Child(int iPos, CTreeNode *newchild);
	void remove1Child(int iPos);
	void clearChildrenForSquash();
	CTreeNode *squash();
	CTreeNode *optimizeTree();

	// for safe read/write

	wstring getName() { return this->name; }
	wstring getText() { return this->text; }
	int getCount() { return this->count; }
	bool getIsRepeating() { return this->is_repeating; }
	bool getHasImplictName() { return this->has_implict_name; }

	int getAttrLen() { return this->attr.getLen(); }
	bool getAttrVal(int iPos, wstring &str1, wstring &str2) { return this->attr.getVal(iPos, str1, str2); }

protected:
	wstring abbrivation;
	CTreeNode *parent;
	vector <CTreeNode *>children;
	int count;
	wstring name;
	wstring text;
	CWstrsStack attr;
	bool is_repeating;
	bool has_implict_name;
};


class CSnippetOrTag {
public:
	CSnippetOrTag();
	virtual ~CSnippetOrTag();

	void setParent(CSnippetOrTag *parent) { this->parent = parent; }

	// initialization

	void InitTag(CTreeNode *node, ZenSyntaxEnum type);
	void InitSnippet(CTreeNode *node, ZenSyntaxEnum type);

	// methods for Tag

	void addChild(CSnippetOrTag *tag);
	void addAttribute(wstring name, wstring value);
	void copyAttributes(CSnippetOrTag *tag);
	void copyAttributes(CTreeNode *node);
	void copyAttributes(CParsedItem *item);
	bool hasTagInContent();

	// for safe read/write

	void setContent(wstring str) { this->_content = replaceUnescapedSymbol(str, wstring(L"|"), global.getCaretPlaceholder()); }
	wstring getContent() { return this->_content; }
	void setPasteContent(wstring val) { this->_paste_content = escapeText(val); }
	wstring getPasteContent() { return this->_paste_content; }
	CSnippetOrTag *findDeepestChild();
	CSnippetOrTag *getParent() { return this->parent; }
	void setLast(CSnippetOrTag *tag) { this->last = tag; }
	CSnippetOrTag *getLast() { return this->last; }
	void setMultiplyElem(CSnippetOrTag *tag) { this->multiply_elem = tag; }
	CSnippetOrTag *getMultiplyElem() { return this->multiply_elem; }
	void setFilters(wstring filters) { this->filters = filters; }
	wstring getFilters() { return this->filters; }
	void setRepeatByLines(bool repeat_by_lines) { this->repeat_by_lines = repeat_by_lines; }
	bool getRepeatByLines() { return this->repeat_by_lines; }
	bool isTag() { return this->bIsTag; }
	bool isSnippet() { return !this->bIsTag; }
	wstring getName() { return this->name; }
	wstring getRealName() { return this->real_name; }
	bool getIsRepeating() { return this->is_repeating; }
	bool getHasImplicitName() { return this->has_implicit_name; }
	CWstrsStack *getAttributes() { return &(this->attr); }
	CParsedItem *get_abbr() { return &(this->_abbr); }
	int getChildCount() { return (int)(this->children.size()); }
	void setNstChild(int iPos, CSnippetOrTag *newchild) {
		if ((!newchild) || (iPos<0) || ((int)(this->children.size())<=iPos)) return;
		if (this->children[iPos])
			delete this->children[iPos];
		this->children[iPos] = newchild;
	}
	CSnippetOrTag *getNstChild(int iPos);
	int getCount() { return this->count; }
	wstring getValue() { return this->value; }

	//no method for Snippet

protected:
	//common ones

	bool bIsTag;
	wstring name;
	wstring real_name;
	int count;
	wstring _content;
	bool repeat_by_lines;
	bool is_repeating;
	CSnippetOrTag *parent;
	CSnippetOrTag *last;
	CSnippetOrTag *multiply_elem;
	ZenSyntaxEnum syntax;
	CWstrsStack attr;
	wstring filters;

	//Tag only

	CParsedItem _abbr;
	wstring _paste_content;
	bool has_implicit_name;

	//Snippet only

	wstring value;
	vector <CSnippetOrTag *>children;
};


class CZenNode {
public:
	CZenNode(CSnippetOrTag *tag);
	virtual ~CZenNode();

	void addChild(CZenNode *tag);
	bool getAttribute(wstring name, wstring &attr);
	bool isUnary();
	bool isInline();
	bool isBlock();
	bool hasTagsInContent();
	bool hasChildren();
	bool hasBlockChildren();
	bool hasBlockSibling() { return this->parent && this->parent->hasBlockChildren(); }
	CZenNode *findDeepestChild();
	wstring toString();
	bool hasOutputPlaceholder();
	void findElementsWithOutputPlaceholder(vector <CZenNode *> &_arr);
	void pasteContent(wstring text);

	// for safe read/write

	void setParent(CZenNode *parent) { this->parent = parent; }
	CZenNode *getParent() { return this->parent; }
	bool getHasImplicitName() { return this->has_implicit_name; }
	void setName(wstring name) { this->name = name; }
	wstring getName() { return this->name; }
	int getChildCount() { return (int)(this->children.size()); }
	CZenNode * getNstChild(int iPos);
	void setPreviousSibling(CZenNode *node) { this->previousSibling = node; }
	CZenNode * getPreviousSibling() { return this->previousSibling; }
	void setNextSibling(CZenNode *node) { this->nextSibling = node; }
	CZenNode * getNextSibling() { return this->nextSibling; }
	void pushChild(CZenNode *node) { this->children.push_back(node); }
	void setContent(wstring content) { this->content = content; }
	wstring getContent() { return this->content; }
	CWstrsStack *getAttributes() { return &(this->attributes); }
	void setCounter(int counter) { this->counter = counter; }
	int getCounter() { return this->counter; }
	void setPadding(wstring padding) { this->padding = padding; }
	wstring getPadding() { return this->padding; }
	bool getIsRepeating() { return this->is_repeating; }
	bool getRepeatByLines() { return this->repeat_by_lines; }
	bool isTag() { return this->bIsTag; }
	bool isSnippet() { return !this->bIsTag; }
	wstring getRealName() { return this->real_name; }
	void setStart(wstring start) { this->start = start; }
	wstring getStart() { return this->start; }
	void setEnd(wstring end) { this->end = end; }
	wstring getEnd() { return this->end; }
	CSnippetOrTag *getSource() { return this->source; }

	// helpers for CProfile

	int getCounterForNode();
	void addComments(int i);
	bool isVeryFirstChild() { return this->parent && !(this->parent->getParent()) && !(this->previousSibling); }

	// in ogirinal js source, only 1 replaceVariables() exists
	// but, in C++ version, splitted to 2 position.
	// this is used with getAttribute() in CZenNode()
	wstring replaceVariables(wstring src);

	int upgradeTabstops(int offset);
	void trimAttribute();

protected:
	bool bIsTag;
	wstring name;
	wstring real_name;
	vector <CZenNode *>children;
	int counter;
	bool is_repeating;
	bool repeat_by_lines;
	bool has_implicit_name;
	CWstrsStack attributes;
	CSnippetOrTag *source;
	CZenNode *parent;
	CZenNode *nextSibling;
	CZenNode *previousSibling;
	wstring start;
	wstring end;
	wstring content;
	wstring padding;
};


class CTreeNodestack {
public:
	CTreeNodestack();
	virtual ~CTreeNodestack();

	void push(CTreeNode *treenode);
	bool pop(CTreeNode * &treenode);
	bool getLast(CTreeNode * &treenode);

protected:
	vector <CTreeNode*> treenodes;
};


// common functions for Zen-coding
////////////////////////////////////////////////////////////

void splitExpression(wstring &str, int &m0s, int &m0l, int &m1s, int &m1l);

wstring getWord(int ix, wstring str);

void extractAttributes(wstring &attr_set, CWstrsStack &strstk);
void parseAttributes(wstring &str, wstring &name, CWstrsStack &strstk);

void appendStack_CssSnippets(CWstrsStack &stack);
void appendStack_HtmlSnippets(CWstrsStack &stack);
void appendStack_HtmlAbbreviations(CWstrsStack &stack);
void appendStack_XslAbbreviations(CWstrsStack &stack);

void AppendResourceChainBase(CWstrsStack &chain, ZenSyntaxEnum syntax, ZenName::Enum name);
void createResourceChain(CWstrsStack &chain, ZenSyntaxEnum syntax, ZenName::Enum name);
bool getAbbreviation(ZenSyntaxEnum type, wstring name, CParsedItem &parseditem);
bool getSnippet(ZenSyntaxEnum type, wstring name, wstring &snippet);
bool getParsedItem(ZenSyntaxEnum syntax, ZenName::Enum name, wstring &item, CParsedItem &parseditem);

bool getVariable(wstring &var, wstring &value);
bool getVariable(wchar_t *var, wstring &value);
wstring getNewLine();

wstring getIndentation();


// parseIntoTree()
////////////////////////////////////////////////////////////

void dumpToken(CTreeNode * context, wstring &token);
CTreeNode * parse(wstring abbr);
bool isSnippet(wstring name, ZenSyntaxEnum type);
CSnippetOrTag *transformTreeNode(CTreeNode *node, ZenSyntaxEnum type);
void processParsedNode(CTreeNode * node, ZenSyntaxEnum type, CSnippetOrTag *parent);
void replaceExpandos(CTreeNode * node, ZenSyntaxEnum type);
CTreeNode * preprocessParsedTree(CTreeNode * tree, ZenSyntaxEnum type);
CSnippetOrTag *parseIntoTree(wstring abbr, ZenSyntaxEnum type);


// rolloutTree()
////////////////////////////////////////////////////////////

bool isItemInCollectionEmpty(wstring name);
bool isItemInCollectionInlineLevel(wstring name);
bool isItemInCollectionBlockLevel(wstring name);
bool hasOutputPlaceholder0(wstring text);
void splitByLines(wstring text, bool remove_empty, vector <wstring> &lines);
CZenNode *rolloutTree(CSnippetOrTag *tree, CZenNode *parent = NULL);


// applyFilters()
////////////////////////////////////////////////////////////

namespace ZenTagOnNewLine {
	enum Enum {
		DECIDE,	FALSE,	TRUE
	};
}

namespace ZenSelfClosingTag {
	enum Enum {
		XHTML,	FALSE,	TRUE
	};
}
class CProfile {
public:
	CProfile(ZenProfileEnum profile = zen_profile_xhtml);
	virtual ~CProfile();
	wstring getFilters() { return this->filters; }
	CZenNode *runFilter(CZenNode *tree, ZenFilters::Enum filter);

protected:
	bool tag_case_is_lower;
	bool attr_case_is_lower;
	bool attr_quotes_is_double;

	ZenTagOnNewLine::Enum tag_nl;

	bool place_cursor;

	// indent tags
	bool indent;

	// how many inline elements should be to force line break
	// (set to 0 to disable)
	int inline_break;

	// use self-closing style for writing empty elements, e.g. <br /> or <br>
	ZenSelfClosingTag::Enum self_closing_tag;

	// Profile-level output filters, re-defines syntax filters
	wstring filters;

	// internal methods
	void setDefaultProfile();
	void setXhtmlProfile();
	void setHtmlProfile();
	void setXmlProfile();
	void setPlainProfile();

	bool shouldBreakLine(CZenNode *node);
	bool shouldBreakChild (CZenNode *node);

	// internal filters
	CZenNode *runFilter_c(CZenNode *tree);
	CZenNode *runFilter_css(CZenNode *tree);
	CZenNode *runFilter_e(CZenNode *tree);
	CZenNode *runFilter_fc(CZenNode *tree);
	CZenNode *runFilter__format(CZenNode *tree, int level=0);
	CZenNode *runFilter_haml(CZenNode *tree, int level=0);
	CZenNode *runFilter_html(CZenNode *tree, int level=0);
	CZenNode *runFilter_s(CZenNode *tree);
	CZenNode *runFilter_t(CZenNode *tree);
	CZenNode *runFilter_xsl(CZenNode *tree);

	CZenNode *processSnippet__format(CZenNode *item, int level);
	CZenNode *processTag__format(CZenNode *item, int level);

	wstring makeAttributesString_haml(CZenNode *tag);
	CZenNode *processSnippet_haml(CZenNode *item, int level);
	CZenNode *processTag_haml(CZenNode *item, int level);

	wstring processStringCase_html(wstring val, bool attr_case_is_lower);
	wstring makeAttributesString_html(CZenNode *tag);
	CZenNode *processSnippet_html(CZenNode *item, int level);
	CZenNode *processTag_html(CZenNode *item, int level);

	wstring placeholder;
	wstring child_token;
	int tabstops;
};

void addFilter(vector <ZenFilters::Enum> &filters, ZenFilters::Enum filter);
void addFilter(vector <ZenFilters::Enum> &filters, wstring filter);
wstring getDefaultFilters(int syntax);
void splitFilterList(wstring &filterlist, vector <ZenFilters::Enum> &filters);
CZenNode *runFilters(CZenNode *tree, CProfile &profile, wstring &filters);
CZenNode *applyFilters(CZenNode *tree, ZenSyntaxEnum syntax, ZenProfileEnum profile, wstring additional_filters);
wstring padString(wstring text, int pad);
wstring padString(wstring text, wstring pad);
wstring escape_fn_4uTS(wchar_t ch);
wstring tabstop_fn_4uTS(wstring num, int &max_num, int offset, wstring value = L"");
int nextWhileIsNumeric(wstring &text, int &il, int ix);
bool braceCheck(wchar_t c, int &brace_count);
int nextWhileBraceCheck(wstring &text, int &il, int &brace_count, int ix);
wstring processTextBeforePaste_4uTS(wstring text, int &offset, int &max_num);


// handleTabStops()
////////////////////////////////////////////////////////////

wstring tabstop_fn_4hTS(int i, wstring num, CWstrsStack &placeholders, size_t &caret_pos, int &selection_len, wstring val = L"");
wstring processTextBeforePaste_4hTS(wstring text, CWstrsStack &placeholders, size_t &caret_pos, int &selection_len);
wstring handleTabStops(wstring text);


////////////////////////////
// 3rd block              //
//      : Implementation  //
////////////////////////////


//////////////////////////////////////
// implementation of global setting //
//////////////////////////////////////

CGlobalSetting::CGlobalSetting() {
	caret_placeholder = L"{%::zen-caret::%}";
}

CGlobalSetting::~CGlobalSetting() {}


///////////////////////////////////////////////////////////////////
// implementation of functions for additional element data types //
///////////////////////////////////////////////////////////////////


// implementation of CIntStack
////////////////////////////////////////////////////////////

CIntStack::CIntStack() {}

CIntStack::~CIntStack() {}

void CIntStack::push(int value) {
	this->ints.push_back(value);
}

bool CIntStack::pop(int &value) {
	int len = (int)(this->ints.size());
	if (!len) return false;

	value = this->ints[len-1];
	this->ints.pop_back();

	return true;
}


// implementation of CWstrsStack
////////////////////////////////////////////////////////////

CWstrsStack::CWstrsStack() {}

CWstrsStack::~CWstrsStack() {}

void CWstrsStack::push(wstring &str1, wstring &str2) {
	this->strs1.push_back(str1);
	this->strs2.push_back(str2);
}

void CWstrsStack::push(wchar_t *str1, wchar_t *str2) {
	wstring wstr1(str1);
	wstring wstr2(str2);

	this->strs1.push_back(wstr1);
	this->strs2.push_back(wstr2);
}

void CWstrsStack::pushWithCheck(wstring &str1, wstring &str2) {
	wstring temp;
	int iPos = this->iFind(str1, temp);
	if (iPos<0) {
		this->strs1.push_back(str1);
		this->strs2.push_back(str2);
	} else {
		this->strs2[iPos] = str2;
	}
}

void CWstrsStack::pushWithCheck(wchar_t *str1, wchar_t *str2) {
	wstring wstr1(str1);
	wstring wstr2(str2);

	wstring temp;
	int iPos = this->iFind(wstr1, temp);

	if (iPos<0) {
		this->strs1.push_back(wstr1);
		this->strs2.push_back(wstr2);
	} else {
		this->strs2[iPos] = wstr2;
	}
}

bool CWstrsStack::pop(wstring &str1, wstring &str2) {
	int len = (int)(this->strs1.size());
	if (!len) return false;

	str1 = this->strs1[len-1];
	this->strs1.pop_back();

	str2 = this->strs2[len-1];
	this->strs2.pop_back();

	return true;
}

void CWstrsStack::removeAll() {
	this->strs1.clear();
	this->strs2.clear();
}

bool CWstrsStack::getVal(int iPos, wstring &str1, wstring &str2) {
	if (iPos<0 || iPos>=(int)(this->strs1.size()))
		return false;

	str1 = this->strs1[iPos];
	str2 = this->strs2[iPos];

	return true;
}

void CWstrsStack::setVal(int iPos, wstring str2) {
	if (iPos<0 || iPos>=(int)(this->strs1.size())) return;

	this->strs2[iPos] = str2;
}

void CWstrsStack::appendStack(CWstrsStack *source) {
	if (!source) return;
	if (!source->getLen()) return;

	int iCount = source->getLen();
	for (int i=0; i<iCount; i++) {
		wstring s1, s2;
		source->getVal(i, s1, s2);
		this->push(s1, s2);
	}
}

void CWstrsStack::appendStack(CWstrsStack &source) {
	this->appendStack(&source);
}

int CWstrsStack::iFind(wstring &str1, wstring &str2) {
	str2 = L"";
	int iLen = this->getLen();

	for (int i=0; i<iLen; i++) {
		if (this->strs1[i] == str1) {
			str2 = this->strs2[i];
			return i;
		}
	}

	return -1;
}

void CWstrsStack::pushAsAttribute(wstring name, wstring value) {
	wstring wtemp;
	int iPos;
	iPos = this->iFind(name, wtemp);
	if (iPos < 0) {
		this->push(name, value);
	} else {
		if (name == L"class") {
			wstring oldname, oldvalue;
			this->getVal(iPos, oldname, oldvalue);

			if (oldvalue.size()) this->setVal(iPos, oldvalue + L" " + value);
			else this->setVal(iPos, value);
		} else {
			this->setVal(iPos, value);
		}
	}
}


// implementation of RegExps
////////////////////////////////////////////////////////////

// /\*(\d+)?$/
int iRegExp00(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int ret;
	if (str[iBegin++] != L'*') return -1;

	ret = 1;
	while ((iBegin<slen) && (str[iBegin] >= L'0') && (str[iBegin] <= L'9')) {
		ret++;
		iBegin++;
	}

	if ((iBegin>=slen) || ISEOL(str[iBegin]))
		return ret;

	return -1;
}

// /^[\w\-:\$]+/
int iRegExp01(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int ret=0;

	while (
		(iBegin<slen) &&
		(ISWORD(str[iBegin]) || (str[iBegin] == L'-') || (str[iBegin] == L':') || (str[iBegin] == L'$'))
		) {
		iBegin++;
		ret++;
	}

	return ret;
}

// /^(["'])((?:(?!\1)[^\\]|\\.)*)\1/
int iRegExp02(wstring &str) {
	int slen = (int)str.size();
	wchar_t quat;
	int ret;

	if (!slen) return -1;
	quat = str[0];

	if ((quat!=L'\"') && (quat!=L'\''))
		return -1;

	ret = 1;
	int iPos = 1;

	while ((iPos<slen) && !ISEOL(str[iPos]) && (str[iPos] != quat)) {
		if (str[iPos] != L'\\') {
			ret++;
			iPos++;
		} else if ((iPos+1<slen) && !ISEOL(str[iPos+1]) && (str[iPos+1] != quat)) {
			ret+=2;
			iPos+=2;
		} else return -1;
	}

	if ((iPos<slen) && str[iPos] == quat) {
		return ret+1;
	}

	return -1;
}

// /(.+?)(\s|$)/
int iRegExp03(wstring &str, int iBegin) {
	// .+? means non-greedy string searching
	// the first \s only can be accepted
	int ret;
	int slen = (int)str.size();

	if (!slen || iBegin<0 || iBegin>=slen) return -1;
	if ((iBegin>=slen) || ISEOL(str[iBegin])) return -1;

	iBegin++;
	ret=1;

	while ((iBegin<slen) && !ISEOL(str[iBegin]) && !ISSPACE(str[iBegin])) {
		iBegin++;
		ret++;
	}

	return ret+1;
}

// /^[\w\d\-_\$\:@!]+\+?$/i
int iRegExp04(wstring &str) {
	int slen = (int)str.size();
	if (!slen) return -1;

	int ret=0;
	int iPos=0;
	while (
		(iPos<slen) &&
		(
			ISWORD(str[iPos]) ||
			(str[iPos] == L'-') || (str[iPos] == L'$') || (str[iPos] == L':') || (str[iPos] == L'@') || (str[iPos] == L'!')
		)) {
		ret++;
		iPos++;
	}

	if ((iPos<slen) && str[iPos] == L'+') {
		ret++;
		iPos++;
	}
	if ((iPos>=slen) || ISEOL(str[iPos]))
		return ret;

	return -1;
}

// (?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)
// used in iRegExp05()
int iRegExp05Sub(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int iCount=0, iPos=iBegin;

	while ((iPos<slen) && ISSPACE(str[iPos])) {
		iCount++;
		iPos++;
	}
	if (!iCount) return -1;

	iCount=0;
	while (
		(iPos<slen) &&
		(
			ISWORD(str[iPos]) ||
			(str[iPos] == L'-') || (str[iPos] == L':')
		)) {
		iCount++;
		iPos++;
	}
	if (!iCount) return -1;

	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;
	if ((iPos>=slen) || str[iPos++] != L'=') return -1;
	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;

	if (iPos>=slen) return -1;
	wchar_t quote = str[iPos++];
	if ((quote != L'\'') && (quote != L'\"')) return -1;

	while ((iPos<slen) && (str[iPos] != quote) && !ISEOL(str[iPos])) iPos++;

	if ((iPos>=slen) || (str[iPos++] != quote)) return -1;

	return iPos-iBegin;
}

// /^<(\w+\:?[\w\-]*)((?:\s+[\w\:\-]+\s*=\s*(['"]).*?\3)*)\s*(\/?)>/
int iRegExp05(wstring &str, wstring &m1, wstring &m2, wstring &m3) {
	int slen = (int)str.size();
	if (!slen || str[0]!=L'<') return -1;

	int iPos=1;

	m1 = L"";
	int m1L=0;
	while ((iPos<slen) && ISWORD(str[iPos])) {
		m1L++;
		m1 += str[iPos++];
	}
	if (!m1L) return -1;

	if ((iPos<slen) && str[iPos]==L':') {
		m1 += L':';
		iPos++;
	}

	while ((iPos<slen) && (ISWORD(str[iPos]) || (str[iPos] == L'-'))) {
		m1 += str[iPos++];
	}

	// finished 1st parenthesis
	// 2nd one is implemented in other function

	m2 = L"";
	int m2L;
	while ((m2L=iRegExp05Sub(str, iPos))>0) {
		m2 += str.substr(iPos, m2L);
		iPos+=m2L;
	}

	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;

	// 3rd parenthesis checks existence of slash

	m3 = L"";
	if ((iPos<slen) && str[iPos]==L'/') {
		m3 = L"/";
		iPos++;
	}

	if ((iPos<slen) && str[iPos]==L'>') {
		return iPos+1;
	}

	return -1;
}

// /([\w\-]+)\s*=\s*(['"])(.*?)\2/g
int iRegExp06(wstring &str, wstring &m1, wstring &m2, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int iPos = iBegin;

	m1 = L"";
	int m1L=0;
	while ((iPos<slen) && (ISWORD(str[iPos]) || (str[iPos] == L'-'))) {
		m1L++;
		m1 += str[iPos++];
	}

	if (!m1L) return -1;

	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;
	if ((iPos>=slen) || str[iPos++] != L'=') return -1;
	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;

	if (iPos>=slen) return -1;
	wchar_t bracket = str[iPos++];
	if ((bracket != L'\'') && (bracket != L'\"'))
		return -1;

	m2 = L"";
	//int m2L=0;
	while (
		(iPos<slen) &&
		(str[iPos] != bracket) && !ISEOL(str[iPos])
		) {
		//m2L++;
		m2 += str[iPos++];
	}

	if ((iPos>=slen) || str[iPos++] != bracket) return -1;

	return iPos-iBegin;
}

// Removes any unnecessary characters from node name
// /(.+)\!$/
int iRegExp07(wstring str) {
	int slen = (int)str.size();

	if (!slen) return 0;
	if (str[slen-1] != L'!') return 0;

	return slen-1;
}

wstring filterNodeName(wstring str) {
	int iUsableLen = iRegExp07(str);
	if (iUsableLen) return str.substr(0, iUsableLen);
	else return str;
}

/** zen_coding.re_tag */
// (?:"[^"]*")
int iRegExp08Sub1(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	if ((iBegin>=slen) || str[iBegin] != L'\"') return -1;

	for (int i=iBegin+1; i<slen; i++) {
		if (ISEOL(str[i])) return -1;
		if (str[i] == L'\"') return (i-iBegin+1);
	}

	return -1;
}

// (?:'[^']*')
int iRegExp08Sub2(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	if (str[iBegin] != L'\'') return -1;

	for (int i=iBegin+1; i<slen; i++) {
		if (ISEOL(str[i])) return -1;
		if (str[i] == L'\'') return (i-iBegin+1);
	}

	return -1;
}

// [^>\s]+
int iRegExp08Sub3(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	for (int i=iBegin; i<slen; i++) {
		if (ISEOL(str[i]) || ISSPACE(str[i]) || str[i]==L'>') {
			if (i==iBegin) return -1;
			else return i-iBegin;
		}
	}

	if (iBegin == slen) return -1;	// actually, this never happens
	else return slen-iBegin;
}

// (?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))
int iRegExp08Sub4(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int iPos=iBegin;
	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;

	if ((iPos>=slen) || str[iPos++] != L'=') return -1;

	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;

	int sublen = iRegExp08Sub1(str, iPos);
	if (sublen<0) {
		sublen = iRegExp08Sub2(str, iPos);
		if (sublen<0) {
			sublen = iRegExp08Sub3(str, iPos);
		}
	}

	if (sublen<0) return -1;

	return (iPos+sublen-iBegin);
}

// (?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)
int iRegExp08Sub5(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int iPos=iBegin;
	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;
	if (iBegin == iPos) return -1;
	if ((iPos>=slen) || ISEOL(str[iPos])) return -1;

	int sublen=0;
	while ((iPos<slen) && ((ISWORD(str[iPos]) || str[iPos]==L'-' || str[iPos]==L':'))) {
		iPos++;
		sublen++;
	}
	if (!sublen) return -1;

	sublen = iRegExp08Sub4(str, iPos);
	if (sublen<0) return -1;

	return (iPos+sublen-iBegin);
}

// /<\/?[\w:\-]+(?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*\s*(\/?)>$/
int iRegExp08(wstring &str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	if (str[iBegin] != L'<') return -1;
	int iPos=iBegin+1;

	if ((iPos<slen) && str[iPos] == L'/') iPos++;

	int sublen=0;
	while ((iPos<slen) && ((ISWORD(str[iPos]) || str[iPos]==L':' || str[iPos]==L'-'))) {
		iPos++;
		sublen++;
	}
	if (!sublen) return -1;

	sublen = iRegExp08Sub5(str, iPos);
	while (sublen>0) {
		iPos += sublen;
		sublen = iRegExp08Sub5(str, iPos);
	}

	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;
	if ((iPos<slen) && str[iPos]==L'/') iPos++;
	if ((iPos>=slen) || str[iPos++]!=L'>') return -1;
	if ((iPos>=slen) || ISEOL(str[iPos])) {
		return (iPos-iBegin);
	} else {
		return -1;
	}
}

// /\|([\w\|\-]+)$/
int iRegExp09(wstring &str, wstring &ret, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	if (str[iBegin] != L'|') return -1;
	int iPos=iBegin+1;
	ret = L"";

	int sublen=0;
	while ((iPos<slen) && ((ISWORD(str[iPos]) || str[iPos]==L'|' || str[iPos]==L'-'))) {
		ret += str[iPos++];
		sublen++;
	}
	if (!sublen) return -1;
	if ((iPos>=slen) || ISEOL(str[iPos])) {
		return iPos-iBegin;
	} else {
		return -1;
	}
}

void moveFilterFromAbbr(wstring &abbr, wstring &filter) {
	int iAbbrLen = (int)abbr.size();
	int iPos;
	bool bFound=false;

	for (iPos=0; iPos<iAbbrLen; iPos++) {
		if (iRegExp09(abbr, filter, iPos)>=0) {
			bFound=true;
			break;
		}
	}

	if (bFound) {
		abbr = abbr.substr(0, iPos);
	} else {
		filter = L"";
	}
}

// filter: fc
// /([\w\-]+\s*:)(?!:)\s*/
int iRegExp10(wstring str, int &iFilteredLen, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int iPos = iBegin;

	iFilteredLen=0;
	while ((iPos<slen) && (ISWORD(str[iPos]) || (str[iPos] == L'-'))) {
		iFilteredLen++;
		iPos++;
	}

	if (!iFilteredLen) return -1;

	while ((iPos<slen) && ISSPACE(str[iPos])) {
		iFilteredLen++;
		iPos++;
	}

	if ((iPos>=slen) || str[iPos] != L':') return -1;
	iFilteredLen++;
	iPos++;

	// calculation of iFilteredLen finished

	int ret = iFilteredLen;

	if ((iPos>=slen) || str[iPos] == L':') return -1;
	while ((iPos<slen) && ISSPACE(str[iPos])) {
		ret++;
		iPos++;
	}

	return ret;
}

// used in replaceVariables()
// /\$\{([\w\-]+)\}/
int iRegExp11(wstring &str, wstring &variable, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int iPos = iBegin;
	int iFilteredLen=0;

	if (str[iPos++] != L'$') return -1;
	iFilteredLen++;

	if ((iPos>=slen) || str[iPos++] != L'{') return -1;
	iFilteredLen++;

	variable = L"";
	while ((iPos<slen) && (ISWORD(str[iPos]) || (str[iPos] == L'-'))) {
		variable += str[iPos++];
		iFilteredLen++;
	}
	if (iFilteredLen<=2) return -1;

	if ((iPos>=slen) || str[iPos] != L'}') return -1;
	iFilteredLen++;

	return iFilteredLen;
}

// used in runFilter_t()
// /^([\s|\u00a0])?[\d|#|\-|\*|\u2022]+\.?\s*/
int iRegExp12(wstring &str, int &begintoremove) {
	int slen = (int)str.size();
	begintoremove=0;

	if (!slen) return -1;
	switch (str[0]) {
	case L' ':
	case L'\x00a0':
		begintoremove=1;
		break;
	}

	int iPos=begintoremove;

	int count=0;
	while (
			(iPos<slen) &&
			(
			ISNUMERIC(str[iPos]) ||
			str[iPos] == L'#' ||
			str[iPos] == L'-' ||
			str[iPos] == L'*' ||
			str[iPos] == L'\x2022'
			)) {
		iPos++;
		count++;
	}
	if (!count) return -1;

	if ((iPos<slen) && str[iPos] == L'.') {
		iPos++;
		count++;
	}

	while ((iPos<slen) && ISSPACE(str[iPos])) {
		iPos++;
		count++;
	}

	return count;
}


// used in runFilter_xsl()
// /\s+select\s*=\s*(['"]).*?\1/
int iRegExp13(wstring str, int iBegin) {
	int slen = (int)str.size();
	if (!slen || iBegin<0 || iBegin>=slen) return -1;

	int iPos=iBegin;
	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;
	if (iPos==iBegin) return -1;

	if (compareWstr(str, wstring(L"select"), iPos)) {
		iPos+=6;
	} else {
		return -1;
	}
	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;

	if ((iPos>=slen) || str[iPos++] != L'=') return -1;
	while ((iPos<slen) && ISSPACE(str[iPos])) iPos++;

	if (iPos>=slen) return -1;
	wchar_t quote=str[iPos++];
	if ((quote != L'\'') && (quote != L'\"')) return -1;

	while ((iPos<slen) && (str[iPos] != quote) && !ISEOL(str[iPos])) iPos++;

	if ((iPos>=slen) || str[iPos++] != quote) return -1;

	return (iPos-iBegin);
}


// implementation of functions for wstring
////////////////////////////////////////////////////////////

int parseInt(wstring &str, int begin, int len) {
	int slen = (int)str.size();
	if (!slen || begin<0 || begin>=slen || !len) return 0;

	int maxlen = slen-begin;

	if (len<0 || len>maxlen)
		len = maxlen;

	if ((str[begin]<L'0') || (str[begin]>L'9'))
		return 0;

	// can't use stoi()
	// cause, WDK doesn't understand it
	int ret=0;
	for (int i=0; i<len; i++) {
		wchar_t ch = str[begin+i];
		if ((ch>=L'0') && (ch<=L'9')) {
			ret = ret*10 + (ch-L'0');
		} else {
			break;
		}
	}

	return ret;
}

bool compareWstr(wstring &origin, wstring compareto, int begin) {
	int slen = (int)origin.size();
	if (!slen || begin<0 || begin>=slen) return false;

	int clen = (int)compareto.size();
	if (!clen || (slen-begin < clen)) return false;

	wstring temp = origin.substr(begin, clen);

	return (temp==compareto);
}

wstring replaceCounter(wstring str, int value) {
	static wstring dollar=L"$", none;
	return replaceUnescapedSymbol(str, dollar, none, ZenReplaceMode::replaceCounter, value);
}

wstring replaceUnescapedSymbol(wstring str, wstring symbol, wstring text, ZenReplaceMode::Enum replacemode, int value) {
	int i = 0,
		il = (int)str.size(),
		sl = (int)symbol.size(),
		match_count = 0;

	wstring wvalue = intToWstr(value);

	while (i < il) {
		if (str[i] == L'\\') {
			// escaped symbol, skip next character
			i += sl + 1;
		} else if (str.substr(i, sl) == symbol) {
			// have match
			int cur_sl = sl;
			match_count++;
			wstring new_value;
			bool bSkip = false;

			switch (replacemode) {
			case ZenReplaceMode::pasteContent:
				// symbol must be L"$#"
				// so, length of symbol is 2
				cur_sl = 2;
				new_value = text;
				break;

			case ZenReplaceMode::replaceCounter:
				if ((i+1<il) && ((str[i+1] == L'{') || ISNUMERIC(str[i+1]))) {
					// it's a variable, skip it
					bSkip = true;
					break;
				}

				// replace sequense of $ symbols with padded number
				{
					int j=i+1;
					while ((j+1<il) && (str[j]==L'$') && (str[j+1]!=L'{')) j++;
					cur_sl = j-i;
					new_value = zeroPadString(wvalue, j-i);
				}
				break;

			default:
				new_value = text;
				break;
			}

			if (bSkip) { // skip replacement
				i++;
				continue;
			}

			str = str.substr(0, i) + new_value + str.substr(i+cur_sl);
			// adjust indexes
			il = (int)str.size();
			i += (int)new_value.size();
		} else {
			i++;
		}
	}

	return str;
}

wstring zeroPadString(wstring str, int pad) {
	wstring padding = L"";
	int il = (int)str.size();

	while (pad > il++) padding += L'0';
	return padding + str;
}

wstring intToWstr(int n) {
	wstringstream ss;
	ss << n;
	return ss.str();
}

/**
 * Escapes special characters used in Zen Coding, like '$', '|', etc.
 * Use this method before passing to actions like "Wrap with Abbreviation"
 * to make sure that existing spacial characters won't be altered
 */
wstring escapeText(wstring text) {
	wstring ret;
	for (int i=0, il=(int)text.size(); i<il; i++) {
		wchar_t ch = text[i];
		if ((ch == L'$') || (ch == L'|') || (ch == L'\\'))
			ret += L'\\';
		ret += ch;
	}

	return ret;
}

/**
 * Unescapes special characters used in Zen Coding, like '$', '|', etc.
 */
wstring unescapeText(wstring text) {
	wstring ret;
	for (int i=0, il=(int)text.size(); i<il; i++) {
		wchar_t ch = text[i];
		if (ch != L'\\') ret += ch;
		else {
			if (i<(il-1)) ret += text[++i];
		}
	}

	return ret;
}

wstring escapeChars(wstring text) {
	wstring ret;
	for (int i=0, il=(int)text.size(); i<il; i++) {
		wchar_t ch = text[i];
		switch (ch) {
		case L'<':
			ret += L"&lt;"; break;
		case L'>':
			ret += L"&gt;"; break;
		case L'&':
			ret += L"&amp;"; break;
		default:
			ret += ch; break;
		}
	}

	return ret;
}

wstring repeatString(wstring str, int how_many) {
	wstring result = L"";
	for (int i = 0; i < how_many; i++)
		result += str;

	return result;
}

void splitWstr(wstring str, wstring seperator, vector <wstring> &result) {
	result.clear();
	size_t slen = str.size();
	size_t splen = seperator.size();
	if (!slen) return;

	if (splen) {
		size_t pos=0;
		while (pos<slen) {
			size_t posfound = str.find(seperator, pos);
			if (posfound == wstring::npos) {
				wstring one = str.substr(pos);
				result.push_back(one);
				break;
			} else {
				wstring one = str.substr(pos, posfound-pos);
				result.push_back(one);
				pos = posfound+splen;
			}
		}
	} else {
		for (size_t i=0; i<slen; i++) {
			wstring one = str.substr(i, 1);
			result.push_back(one);
		}
	}
}

wstring joinWstr(vector <wstring> &array, wchar_t *seperator) {
	wstring ret;
	int arraysize = (int)array.size();
	switch (arraysize) {
	case 0:
		return ret;
		break;

	case 1:
		return array[0];
		break;
	}

	if (seperator && seperator[0]) {
		for (int i=0; i<arraysize-1; i++) {
			ret += (array[i] + seperator);
		}
		ret += array[arraysize-1];
	} else {
		for (int i=0; i<arraysize; i++) {
			ret += array[i];
		}
	}

	return ret;
}

bool compareWstrSub(wstring &src, int pos, wstring &tocompare, bool caseinsensitive) {
	int srclen = (int)src.size();
	if (!srclen || pos<0 || pos>=srclen) return false;
	int tocomparelen = (int)tocompare.size();
	if (!tocomparelen) return false;
	if (pos+tocomparelen > srclen) return false;

	wstring temp = src.substr(pos, tocomparelen);
	if (caseinsensitive) makeLowerWstr(temp);

	return (tocompare == temp);
}

wstring replaceWstr(wstring src, wstring tofind, wstring toreplace, bool global, bool caseinsensitive) {
	if (!tofind.size()) return src;
	if (caseinsensitive) makeLowerWstr(tofind);
	int tofindlenminus1 = (int)tofind.size()-1;

	wstring ret;
	for (int i=0, il=(int)src.size(); i<il; i++) {
		if (compareWstrSub(src, i, tofind, caseinsensitive)) {
			ret += toreplace;
			i+=tofindlenminus1;
			if (!global) {
				ret += src.substr(i+1);
				break;
			}
		} else {
			ret += src[i];
		}
	}

	return ret;
}

// in ogirinal js source, only 1 replaceVariables() exists
// but, in C++ version, splitted to 2 position.
// this is used with getVariable() and vars==NULL
wstring replaceVariables(wstring src) {
	// L1851

	wstring ret;

	for (int i=0, il=(int)src.size(); i<il; i++) {
		wstring variable;
		int iFilteredLen = iRegExp11(src, variable, i);

		if (iFilteredLen>3) {
			wstring attr;

			if (getVariable(variable, attr)) {
				ret += attr;
			} else {
				ret += (L"${" + variable + L"}");
			}

			i+=(iFilteredLen-1);
		} else {
			ret += src[i];
		}
	}

	return ret;
}

void makeUpperWstr(wstring &text) {
	for (int i=0, il=(int)text.size(); i<il; i++) {
		if (ISLOWERCASE(text[i]))
			text[i] -= ((L'a') - (L'A'));
	}
}

void makeLowerWstr(wstring &text) {
	for (int i=0, il=(int)text.size(); i<il; i++) {
		if (ISUPPERCASE(text[i]))
			text[i] += ((L'a') - (L'A'));
	}
}

wstring ltrimWstr(wstring str) {
	int slen = (int)str.size();
	if (!slen) return str;

	int begin = 0;
	while (begin<slen && ISSPACE(str[begin])) {
		begin++;
	}

	return str.substr(begin);
}

wstring removenewlines(wstring str) {
	wstring ret;
	for (int i=0, il=(int)str.size(); i<il; i++) {
		wchar_t ch = str[i];
		if (!ISCRORLF(ch)) ret+=ch;
	}

	return ret;
}

void subwstr(wstring &str, int begin, int len) {
	int slen = (int)str.size();
	if (!slen || begin<0 || begin>=slen || len==0) {
		str = L"";
		return;
	}

	if (begin+len > (int)str.size() || len<0)
		len = (int)str.size()-begin;

	str = str.substr(begin, len);
}

wstring trimWstr(wstring str) {
	int slen = (int)str.size();

	int begin=0;
	while (begin<slen && ISSPACE(str[begin]))
		begin++;

	if (begin >= slen)
		return wstring(L"");

	int end = slen-1;
	while (ISSPACE(str[end]))
		end--;

	return str.substr(begin, end-begin+1);
}

bool findNewLine(wstring &str, int begin, int &pos, int &len) {
	int slen = (int)str.size();
	if (begin<0 || begin>=slen) return false;

	int iFindType=0;
	for (int i=begin; i<slen; i++) {
		if (str[i] == L'\n') {
			pos = i;
			iFindType = 1;
			break;
		} else if (str[i] == L'\r') {
			pos = i;
			iFindType = 2;
			break;
		}
	}

	if (!iFindType) return false;

	if (
		((iFindType == 1) && (pos+1<slen) && (str[pos+1] == L'\r')) ||
		((iFindType == 2) && (pos+1<slen) && (str[pos+1] == L'\n'))
		) {
		len=2;
	} else {
		len=1;
	}

	return true;
}

void replaceTab2DblSpace(wstring &str) {
	size_t slen=str.size();

	wstring oldstr = str;
	str = L"";

	for (size_t i=0; i<slen; i++) {
		size_t tabpos = oldstr.find(L"\t", i);
		if (tabpos == wstring::npos) {
			str += oldstr.substr(i);
			break;
		}

		str += oldstr.substr(i, tabpos-i);
		str += L"  ";
		i = tabpos;
	}
}

void replace2CRLF(wstring &str) {
	size_t slen=str.size();

	wstring oldstr = str;
	str = L"";

	for (size_t i=0; i<slen; i++) {
		int pos, len;
		if (findNewLine(oldstr, (int)i, pos, len)) {
			str += (oldstr.substr(i, pos-i) + L"\r\n");
			i = pos+len-1;
		} else {
			str += oldstr.substr(i);
			break;
		}
	}
}


/////////////////////////////////////////////////
// implementation of data types for Zen-coding //
/////////////////////////////////////////////////


// implementation of CParsedItem class
////////////////////////////////////////////////////////////

CParsedItem::CParsedItem() {
	this->type = ZenType::undefined;
	this->is_empty = false;
}

CParsedItem::~CParsedItem() {}

void CParsedItem::cleanUp(void) {
	this->key = L"";
	this->name = L"";
	this->value = L"";
	this->type = ZenType::undefined;
	this->is_empty = false;
	this->attributes.removeAll();
}

void CParsedItem::setType(ZenType::Enum type) {
	this->type = type;
}

void CParsedItem::setTypeKeyValue(ZenType::Enum type, wstring key, wstring value) {
	this->type = type;
	this->key = key;
	this->value = value;
}

void CParsedItem::setNameIsempty(wstring name, bool is_empty) {
	this->name = name;
	this->is_empty = is_empty;
}

void CParsedItem::setValueOnly(wstring value) {
	this->type = ZenType::valueonly;
	this->value = value;
}

void CParsedItem::appendAttribute(wstring &attrs) {
	int iLen = (int)attrs.size();
	if (!iLen) return;

	for (int i=0; i<iLen; i++) {
		wstring m1, m3;
		int iRegExpSize = iRegExp06(attrs, m1, m3, i);
		if (iRegExpSize<0) continue;

		this->attributes.push(m1, m3);

		i+=(iRegExpSize-1);
	}
}

void CParsedItem::getValues(ZenType::Enum &type, wstring &key, wstring &value, wstring &name, bool &is_empty) {
	type = this->type;
	key = this->key;
	value = this->value;
	name = this->name;
	is_empty = this->is_empty;
}

CWstrsStack *CParsedItem::getAttributes() {
	return &(this->attributes);
}

void CParsedItem::copyFrom(CParsedItem *oldone) {
	ZenType::Enum type;
	wstring key, value, name;
	bool is_empty;

	oldone->getValues(type, key, value, name, is_empty);

	this->type = type;
	this->key = key;
	this->value = value;
	this->name = name;
	this->is_empty = is_empty;

	this->attributes.removeAll();
	this->attributes.appendStack(oldone->getAttributes());
}

void CParsedItem::entry(ZenType::Enum type, wstring &key, wstring &value) {
	// L903

	this->cleanUp();

	this->setTypeKeyValue(type, key, value);
}

void CParsedItem::makeExpando(wstring &key, wstring &value) {
	// L917

	this->cleanUp();

	this->setTypeKeyValue(ZenType::expando, key, value);
}

void CParsedItem::makeAbbreviation(wstring &key, wstring &tag_name, wstring &attrs, bool is_empty) {
	// L929

	this->cleanUp();

	this->setNameIsempty(tag_name, !!is_empty);

	this->appendAttribute(attrs);
	this->setType(ZenType::tag);
}

void CParsedItem::parseAbbreviation(wstring key, wstring value) {
	// L955

	key = trimWstr(key);
	int iKeyLen = (int)key.size();

	int re_tagSize;
	wstring m1, m2, m4;

	if (iKeyLen && (key[iKeyLen-1]==L'+')) {
		// this is expando, leave 'value' as is
		this->makeExpando(key, value);
	} else if ((re_tagSize = iRegExp05(value, m1, m2, m4))>0) {
		this->makeAbbreviation(key, m1, m2, m4==L"/");
	} else {
		// assume it's reference to another abbreviation
		this->entry(ZenType::reference, key, value);
	}
}


// implementation of CTreeNode class
////////////////////////////////////////////////////////////

CTreeNode::CTreeNode() {
	// L1108

	this->parent = NULL;
	this->count = 1;
	this->is_repeating = false;
	this->has_implict_name = false;
}

CTreeNode::~CTreeNode() {
	if (!this) return;

	for (int i=0, il=(int)(this->children.size()); i<il; i++)
	{
		if (this->children[i])
			delete this->children[i];
	}
}

CTreeNode * CTreeNode::addChild(CTreeNode *newchild) {
	// L1127

	if (newchild == NULL)
		newchild = new CTreeNode();

	newchild->parent = this;

	this->children.push_back(newchild);

	return newchild;
}

void CTreeNode::replace(CTreeNode *newNode) {
	// L1138

	CTreeNode *parent = this->parent;
	if (parent && (parent->children.size())) {
		for (int i=0; i<(int)(parent->children.size()); i++) {
			if (parent->children[i] == this) {
				parent->children[i] = newNode;
				newNode->parent = parent;

				delete this;
				return;
			}
		}
	}
}

bool CTreeNode::setAbbreviation(wstring abbr) {
	// L1155

	int ilen = (int)abbr.size();
	int m0p, m0l=-1;
	wstring zcAbbr(abbr);

	this->abbrivation = abbr;

	//var m = abbr.match(/\*(\d+)?$/);

	for (m0p=0; m0p<ilen; m0p++) {
		if ((m0l = iRegExp00(abbr, m0p))>0) {
			break;
		}
	}

	if (m0l>0) {
		this->count = parseInt(abbr, m0p+1, m0l-1);
		if ((this->count)<=0) this->count = 1;

		this->is_repeating = (!m0l);
		subwstr(zcAbbr, 0, m0p);
	}

	if (zcAbbr.size()) {
		int m1p, m1l;
		wstring name;

		splitExpression(zcAbbr, m0p, m0l, m1p, m1l);
		name = zcAbbr;
		subwstr(name, m0p, m0l);
		if (m1p>=0) {
			this->text = zcAbbr;
			subwstr(this->text, m1p, m1l);
		}

		if (name.size()>0) {
			wstring extractedname;
			CWstrsStack attr_result;

			parseAttributes(name, extractedname, attr_result);
			if (extractedname.size()) {
				this->name = extractedname;
				this->has_implict_name = false;
			} else {
				this->name = L"div";
				this->has_implict_name = true;
			}

			this->attr.removeAll();
			this->attr.appendStack(&attr_result);
		}
	}

	// validate name
	if ((this->name).size() && iRegExp04(this->name)<=0)
		return false;	// exception "InvalidAbbreviation"

	return true;
}

CTreeNode * CTreeNode::copy() {
	if (!this) return NULL;

	CTreeNode * newnode = new CTreeNode();

	newnode->abbrivation = this->abbrivation;
	newnode->name = this->name;
	newnode->text = this->text;

	newnode->attr.appendStack(this->attr);

	newnode->parent = this->parent;
	// eventually, useless
	for (int i=0; i<(int)(this->children.size()); i++) {
		newnode->children.push_back(this->children[i]);
	}

	newnode->count = this->count;
	newnode->is_repeating = this->is_repeating;
	newnode->has_implict_name = this->has_implict_name;

	return newnode;
}

bool CTreeNode::hasEmptyChildren() {
	// L1229

	int childcount = (int)(this->children.size());
	if (!childcount) return false;

	for (int i=0; i<childcount; i++)
	{
		if (this->children[i]->isEmpty()) return true;
	}

	return false;
}

CTreeNode * CTreeNode::getNstChild(int iPos) {
	if (iPos<0 || ((int)(this->children.size()))<=iPos) return NULL;
	return this->children[iPos];
}

void CTreeNode::insert1Child(int iPos, CTreeNode *newchild) {
	if (iPos<0) return;

	vector <CTreeNode *>::iterator it;
	it = this->children.begin()+iPos;

	this->children.insert(it, newchild);
}

void CTreeNode::remove1Child(int iPos) {
	if (iPos<0) return;

	vector <CTreeNode *>::iterator it;
	it = this->children.begin()+iPos;

	this->children.erase(it);
}

void CTreeNode::clearChildrenForSquash() {
	int count = (int)(this->children.size());
	for (int i=0; i<count; i++)
		this->children[i] = NULL;
}

CTreeNode * CTreeNode::squash() {
	// L1271

	for (int i=(int)(this->children.size())-1; i>=0; i--) {
		/** @type {TreeNode} */
		CTreeNode *n = this->children[i];
		if (n->isEmpty()) {
			int childcount = (int)(n->children.size());
			for (int j=0; j<childcount; j++) {
				this->insert1Child(i+1+j, n->children[j]);
			}
			this->children[i]->clearChildrenForSquash();
			this->remove1Child(i);
		}
	}
	return this;
}

CTreeNode * CTreeNode::optimizeTree() {
	// L1437

	while (this->hasEmptyChildren())
		this->squash();

	for (int i=0; i<(int)(this->children.size()); i++) {
		this->children[i]->optimizeTree();
	}

	return this;
}


// implementation of CSnippetOrTag class
////////////////////////////////////////////////////////////

CSnippetOrTag::CSnippetOrTag() {
	// initialize common ones

	this->bIsTag = false;
	//this->name = L"";
	//this->real_name = L"";
	this->count = 0;
	//this->_content = L"";
	this->repeat_by_lines = false;
	this->is_repeating = false;
	this->parent = NULL;
	this->last = NULL;
	this->multiply_elem = NULL;
	this->syntax = zen_syntax_html;
	//this->attr.removeAll();
	//this->filters = L"";

	// initialize Tag ones

	//this->_abbr;
	//this->_paste_content = L"";
	this->has_implicit_name = false;

	// initialize Snippet ones

	//this->value = L"";
	//this->children.clear();
}

CSnippetOrTag::~CSnippetOrTag() {
	if (!this) return;

	for (int i=0, il=(int)(this->children.size()); i<il; i++) {
		delete this->children[i];
	}
}


void CSnippetOrTag::InitTag(CTreeNode *node, ZenSyntaxEnum type) {
	// L1903
	this->bIsTag = true;

	if ((type != zen_syntax_css) && (type != zen_syntax_xml) && (type != zen_syntax_xsl))
		type = zen_syntax_html;

	CParsedItem abbr;
	bool bAbbrOk = false;

	if (node && node->getName().size()) {
		if (getAbbreviation(type, filterNodeName(node->getName()), abbr)) {
			bAbbrOk = true;
			if (abbr.getType() == ZenType::reference) {
				wstring abbr_value;
				abbr_value = abbr.getValue();
				bAbbrOk = getAbbreviation(type, filterNodeName(abbr_value), abbr);
			}
		}
	}

	if (bAbbrOk) {
		this->name = abbr.getName();
	} else {
		if (node) this->name = node->getName();
	}

	if (node) this->real_name = node->getName();
	if (node) this->count = node->getCount();
	this->_abbr.copyFrom(&abbr);
	this->syntax = type;
	this->_content = L"";
	this->_paste_content = L"";

	if (node) this->repeat_by_lines = node->getIsRepeating();
	this->is_repeating = (node) && (node->getCount() > 1);
	this->parent = NULL;
	if (node) this->has_implicit_name = node->getHasImplictName();

	if (node) this->setContent(node->getText());

	// add default attributes
	/*
	if (this._abbr)
		this.copyAttributes(this._abbr.value);
	*/
	this->copyAttributes(&(this->_abbr));

	if (node) this->copyAttributes(node);
}

void CSnippetOrTag::InitSnippet(CTreeNode *node, ZenSyntaxEnum type) {
	this->bIsTag = false;

	this->name = filterNodeName(node->getName());
	this->real_name = node->getName();
	this->children.clear();
	this->count = node->getCount();
	this->_content = node->getText();
	this->repeat_by_lines = node->getIsRepeating();
	this->is_repeating = (node) && (node->getCount() > 1);
	this->parent = NULL;
	this->has_implicit_name = node->getHasImplictName();
	this->is_repeating = (node) && (node->getCount() > 1);
	this->attr.removeAll();
	{
		wstring snippet;
		getSnippet(type, this->name, snippet);
		this->value = replaceUnescapedSymbol(snippet, wstring(L"|"), global.getCaretPlaceholder());
	}
	this->parent = NULL;
	this->has_implicit_name = node->getHasImplictName();
	this->syntax = type;
	this->addAttribute(wstring(L"id"), global.getCaretPlaceholder());
	this->addAttribute(wstring(L"class"), global.getCaretPlaceholder());
	this->copyAttributes(node);
}


// methods for Tag
void CSnippetOrTag::addChild(CSnippetOrTag *tag) {
	tag->setParent(this);
	this->children.push_back(tag);
}

void CSnippetOrTag::addAttribute(wstring name, wstring value) {
	value = replaceUnescapedSymbol(value, wstring(L"|"), global.getCaretPlaceholder());

	this->attr.pushAsAttribute(name, value);
}

void CSnippetOrTag::copyAttributes(CSnippetOrTag *tag) {
	// L1982

	if (!tag) return;

	int il = tag->attr.getLen();
	wstring name, value;

	for (int i=0; i<il; i++) {
		tag->attr.getVal(i, name, value);
		this->attr.pushAsAttribute(name, value);
	}
}

void CSnippetOrTag::copyAttributes(CTreeNode *node) {
	// L1982

	int il = node->getAttrLen();
	wstring name, value;

	for (int i=0; i<il; i++) {
		node->getAttrVal(i, name, value);
		this->attr.pushAsAttribute(name, value);
	}
}

void CSnippetOrTag::copyAttributes(CParsedItem *item) {
	// L1982

	CWstrsStack *attrs = item->getAttributes();
	int il = attrs->getLen();
	wstring name, value;

	for (int i=0; i<il; i++) {
		attrs->getVal(i, name, value);
		this->attr.pushAsAttribute(name, value);
	}
}

bool CSnippetOrTag::hasTagInContent() {
	// L1994
	wstring content = this->getContent();
	return (iRegExp08(content)>=0);
}

CSnippetOrTag * CSnippetOrTag::findDeepestChild() {
	// L2259

	if (this->children.size() == 0) return NULL;

	CSnippetOrTag * deepest_child = this;
	while (true) {
		deepest_child = deepest_child->children[deepest_child->children.size()-1];
		if (deepest_child->children.size() == 0) break;
	}

	return deepest_child;
}

CSnippetOrTag * CSnippetOrTag::getNstChild(int iPos) {
	if (iPos<0 || ((int)(this->children.size()))<=iPos) return NULL;
	return this->children[iPos];
}


// implementation of CZenNode class
////////////////////////////////////////////////////////////

CZenNode::CZenNode(CSnippetOrTag *tag) {
	this->bIsTag = tag->isTag();
	this->name = tag->getName();
	this->real_name = tag->getRealName();
	//this->children.clear();
	this->counter = 1;
	this->is_repeating = tag->getIsRepeating();
	this->repeat_by_lines = tag->getRepeatByLines();
	this->has_implicit_name = (((tag->isTag())) && (tag->getHasImplicitName()));

	// create deep copy of attribute list so we can change
	// their values in runtime without affecting other nodes
	// created from the same tag
	//this->attributes.removeAll();
	this->attributes.appendStack(tag->getAttributes());

	/** @type {Tag} Source element from which current tag was created */
	this->source = tag;

	// relations
	/** @type {ZenNode} */
	this->parent = NULL;
	/** @type {ZenNode} */
	this->nextSibling = NULL;
	/** @type {ZenNode} */
	this->previousSibling = NULL;

	// output params
	this->start = L"";
	this->end = L"";
	this->content = tag->getContent();
	this->padding = L"";
}

CZenNode::~CZenNode() {
	if (!this) return;

	for (int i=0, il=(int)(this->children.size()); i<il; i++) {
		delete this->children[i];
	}
}

void CZenNode::addChild(CZenNode *tag) {
	tag->setParent(this);

	// check for implicit name
	if (tag->getHasImplicitName() && this->isInline())
		tag->setName(wstring(L"span"));

	CZenNode *last_child = this->getNstChild(this->getChildCount()-1);
	if (last_child) {
		tag->setPreviousSibling(last_child);
		last_child->setNextSibling(tag);
	}

	this->pushChild(tag);
}

bool CZenNode::getAttribute(wstring name, wstring &attr) {
	makeLowerWstr(attr);
	return (this->attributes.iFind(name, attr) >= 0);
}

bool CZenNode::isUnary() {
	// L2197

	if (!this->bIsTag)
		return false;

	CParsedItem *source_abbr = this->source->get_abbr();
	return (((source_abbr->getType() != ZenType::undefined) && (source_abbr->getIsEmpty()))
		|| isItemInCollectionEmpty(this->name));
}

bool CZenNode::isInline() {
	// L2209

	return isItemInCollectionInlineLevel(this->name);

	// this.type can never be 'text'. WHAT ARE YOU?
	/*
	return this.type == 'text'
		|| zen_resources.isItemInCollection(this.source.syntax, 'inline_level', this.name);
	*/
}

bool CZenNode::isBlock() {
	// L2218

	return (!this->bIsTag) || (!this->isInline());
}

bool CZenNode::hasTagsInContent() {
	// L2226

	wstring content = this->content;
	for (int i=0, il=(int)content.size(); i<il; i++)
	{
		if (iRegExp08(content, i)>=0) return true;
	}
	return false;
}

bool CZenNode::hasChildren() {
	// L2234

	return (this->getChildCount())!=0;
}

bool CZenNode::hasBlockChildren() {
	// L2242

	if (this->hasTagsInContent() && this->isBlock()) {
		return true;
	}

	for (int i = 0; i < this->getChildCount(); i++) {
		if (this->getNstChild(i)->isBlock())
			return true;
	}

	return false;
}

CZenNode *CZenNode::findDeepestChild() {
	// L2259

	if (!this->getChildCount())
		return NULL;

	CZenNode *deepest_child = this;
	while (true) {
		deepest_child = deepest_child->getNstChild(deepest_child->getChildCount()-1);
		if (!deepest_child->getChildCount())
			break;
	}

	return deepest_child;
}

wstring CZenNode::toString() {
	// L2276

	wstring content = L"";
	for (int i = 0, il = this->getChildCount(); i < il; i++) {
		content += this->getNstChild(i)->toString();
	}

	return this->start + this->content + content + this->end;
}

bool CZenNode::hasOutputPlaceholder() {
	// L2289

	if (hasOutputPlaceholder0(this->content)) {
		return true;
	} else {
		// search inside attributes
		for (int i = 0, il = this->attributes.getLen(); i < il; i++) {
			wstring name, value;
			this->attributes.getVal(i, name, value);
			if (hasOutputPlaceholder0(value))
				return true;
		}
	}

	return false;
}

void CZenNode::findElementsWithOutputPlaceholder(vector <CZenNode *> &_arr) {
	// L2309

	for (int i = 0, il = this->getChildCount(); i < il; i++) {
		CZenNode *child = this->getNstChild(i);
		if (child->hasOutputPlaceholder()) {
			_arr.push_back(child);
		}
		child->findElementsWithOutputPlaceholder(_arr);
	}
}

void CZenNode::pasteContent(wstring text) {
	// L2329

	vector <CZenNode *>items;

	if (this->hasOutputPlaceholder())
		items.push_back(this);

	this->findElementsWithOutputPlaceholder(items);

	if (items.size()) {
		for (int i = 0, il = (int)items.size(); i < il; i++) {
			/** @type {ZenNode} */
			CZenNode *item = items[i];

			item->setContent(replaceUnescapedSymbol(item->getContent(), wstring(L"$#"), text, ZenReplaceMode::pasteContent));
			for (int j = 0, jl = item->getAttributes()->getLen(); j < jl; j++) {
				wstring name, value;
				item->getAttributes()->getVal(j, name, value);
				value = replaceUnescapedSymbol(value, wstring(L"$#"), text, ZenReplaceMode::pasteContent);
				item->getAttributes()->setVal(j, value);
			}
		}
	} else {
		// no placeholders found, add content to the deepest child
		CZenNode *child = this->findDeepestChild();
		if (!child) child = this;
		child->setContent(child->getContent()+text);
	}
}

CZenNode * CZenNode::getNstChild(int iPos) {
	if (iPos<0 || ((int)(this->children.size()))<=iPos) return NULL;
	return this->children[iPos];
}


// helpers for CProfile

int CZenNode::getCounterForNode() {
	// find nearest repeating parent
	int counter = this->getCounter();
	if (!this->getIsRepeating() && !this->getRepeatByLines()) {
		CZenNode *temp = this;
		while (temp = temp->getParent()) {
			if (temp->getIsRepeating() || temp->getRepeatByLines())
				return temp->getCounter();
		}

	}
	return counter;
}

void CZenNode::addComments(int i) {
	wstring id_attr, class_attr, nl;
	wstring name;

	name = L"id";
	this->attributes.iFind(name, id_attr);

	name = L"class";
	this->attributes.iFind(name, class_attr);

	nl = getNewLine();

	if (id_attr.size() || class_attr.size()) {
		wstring comment_str;
		wstring padding;
		if (this->getParent()) padding = this->getParent()->getPadding();
		if (id_attr.size()) comment_str += (L"#" + id_attr);
		if (class_attr.size()) comment_str += (L"." + class_attr);

		size_t pos = this->start.find(L"<");
		if (pos != wstring::npos) {
			this->start = this->start.replace(pos, 1, L"<!-- " + comment_str + L" -->" + nl + padding + L"<");
		}

		pos = this->end.find(L">");
		if (pos != wstring::npos) {
			this->end = this->end.replace(pos, 1, L">" + nl + padding + L"<!-- /" + comment_str + L" -->");
		}

		// replace counters
		int counter = this->getCounterForNode();
		this->start = replaceCounter(this->start, counter);
		this->end = replaceCounter(this->end, counter);
	}
}

// in ogirinal js source, only 1 replaceVariables() exists
// but, in C++ version, splitted to 2 position.
// this is used with getAttribute() in CZenNode()
wstring CZenNode::replaceVariables(wstring src) {
	// L1851

	wstring ret;

	for (int i=0, il=(int)src.size(); i<il; i++) {
		wstring variable;
		int iFilteredLen = iRegExp11(src, variable, i);

		if (iFilteredLen>3) {
			wstring attr;

			if (this->getAttribute(variable, attr)) {
				ret += attr;
			} else {
				ret += (L"${" + variable + L"}");
			}

			i+=(iFilteredLen-1);
		} else {
			ret += src[i];
		}
	}

	return ret;
}

int CZenNode::upgradeTabstops(int offset) {
	// L2957
	int max_num = 0;
	this->setStart(processTextBeforePaste_4uTS(this->getStart(), offset, max_num));
	this->setEnd(processTextBeforePaste_4uTS(this->getEnd(), offset, max_num));
	this->setContent(processTextBeforePaste_4uTS(this->getContent(), offset, max_num));

	return max_num;
}

void CZenNode::trimAttribute() {
	// L7515
	wstring start = this->getStart();
	int len = (int)start.size();
	for (int i=0; i<len; i++) {
		int regexplen = iRegExp13(start, i);
		if (regexplen>0) {
			start.erase(i, regexplen);
			this->setStart(start);
			return;
		}
	}
}


// implementation of CTreeNodestack class
////////////////////////////////////////////////////////////

CTreeNodestack::CTreeNodestack() {}

CTreeNodestack::~CTreeNodestack() {}

void CTreeNodestack::push(CTreeNode *treenode) {
	if (!treenode) return;

	this->treenodes.push_back(treenode);
}

bool CTreeNodestack::pop(CTreeNode * &treenode) {
	int iCount = (int)(this->treenodes.size());

	if (!iCount) {
		return false;
	}

	treenode = this->treenodes[iCount-1];
	this->treenodes.pop_back();

	return true;
}

bool CTreeNodestack::getLast(CTreeNode * &treenode) {
	int iCount = (int)(this->treenodes.size());

	if (!iCount) {
		return false;
	}

	treenode = this->treenodes[iCount-1];

	return true;
}


// implementation of common functions for Zen-coding
////////////////////////////////////////////////////////////

void splitExpression(wstring &expr, int &m0p, int &m0l, int &m1p, int &m1l) {
	// L1456

	int attr_lvl = 0;
	int text_lvl = 0;
	int i = 0;
	int il = (int)expr.size();

	wchar_t ch;
	CIntStack stack;

	if (expr.find(L'{') == wstring::npos) {
		m0p = 0;
		m0l = (int)expr.size();
		m1p = -1;
		m1l = -1;

		return;
	}

	while (i<il) {
		ch = expr[i];
		switch (ch) {
		case L'[':
			if (!text_lvl)
				attr_lvl++;
			break;

		case L']':
			if (!text_lvl)
				attr_lvl--;
			break;

		case L'{':
			if (!attr_lvl) {
				text_lvl++;
				stack.push(i);
			}
			break;

		case L'}':
			if (!attr_lvl) {
				int brace_start;
				text_lvl--;
				if (stack.pop(brace_start) && text_lvl==0) {
					m0p = 0;
					m0l = brace_start;

					m1p = brace_start+1;
					m1l = i-brace_start-1;

					return;
				}
			}
			break;
		}
		i++;
	}

	m0p = 0;
	m0l = (int)expr.size();
	m1p = -1;
	m1l = -1;
}

wstring getWord(int ix, wstring str) {
	// L1300

	int iRegExplen = iRegExp01(str, ix);
	if (iRegExplen<0) {
		return wstring(L"");
	} else {
		return str.substr(ix, iRegExplen);
	}
}

void extractAttributes(wstring &attr_set, CWstrsStack &strstk) {
	// L1309

	int loop_count = 100; // endless loop protection
	wstring attr_set0(attr_set);
	wstring attr_name;
	wstring attr_value;

	attr_set0 = trimWstr(attr_set0);

	while (attr_set0.size() && loop_count--) {
		wchar_t ch;

		attr_name = getWord(0, attr_set0);

		if (!attr_name.size()) break;

		ch = attr_set0[attr_name.size()];

		if (ch == L'=') {
			wchar_t ch2;
			ch2 = attr_set0[attr_name.size()+1];

			if ((ch2 == L'\"') || (ch2 == L'\'')) {
				// we have a quoted string
				wstring attr_set_sub = attr_set0.substr(attr_name.size()+1);
				int iMatchLen;

				iMatchLen = iRegExp02(attr_set_sub);

				if (iMatchLen>1) {
					attr_value = attr_set_sub.substr(1, iMatchLen-2);
					subwstr(attr_set0, (int)attr_name.size()+iMatchLen+1);
					attr_set0 = trimWstr(attr_set0);
				} else {
					// something wrong, break loop
					attr_set0 = L"";
				}
			} else {
				// unquoted string
				wstring attr_set_sub = attr_set0.substr(attr_name.size()+1);
				int i;
				int iMatchLen=-1;

				for (i=0; i<(int)attr_set_sub.size(); i++) {
					if ((iMatchLen = iRegExp03(attr_set_sub, i))>0) break;
				}

				if (iMatchLen>0) {
					// #i ~ +iMatchLen count matches
					// the last one must be removed
					attr_value = attr_set_sub.substr(i, iMatchLen-1);
					subwstr(attr_set0, (int)attr_name.size()+iMatchLen);
					attr_set0 = trimWstr(attr_set0);
				} else {
					attr_set0 = L"";
				}

			}
		} else {
			subwstr(attr_set0, (int)attr_name.size());
			attr_set0 = trimWstr(attr_set0);
		}

		strstk.push(attr_name, attr_value);
	}
}

void parseAttributes(wstring &str, wstring &name, CWstrsStack &strstk) {
	// L1367
	bool collect_name = true;
	int i=0;
	int il = (int)str.size();
	wstring val;
	wstring class_name;

	strstk.removeAll();

	// walk char-by-char
	while (i<il) {
		wchar_t ch = str[i];
		switch (ch) {
		case L'#':	// id
			{
				val = getWord(i, str.substr(1));
				static wstring wid(L"id");
				strstk.push(wid, val);
				i += ((int)val.size()+1);
				collect_name = false;
			}

			break;

		case L'.':	// class
			val = getWord(i, str.substr(1));
			if (!class_name.size()) {
				class_name = val;
			} else {
				class_name += L' ';
				class_name += val;
			}

			i += ((int)val.size()+1);
			collect_name = false;

			break;

		case L'[':	//begin attribute set
			{
				// search for end of set
				int end_ix = (int)str.find(L']');
				if (end_ix == wstring::npos) {
					i = (int)str.size();
				} else {
					wstring str0 = str.substr(i + 1, end_ix);
					CWstrsStack strstk2;
					extractAttributes(str0, strstk2);

					strstk.appendStack(strstk2);
					i = end_ix;
				}
				collect_name = false;
			}
			break;

		default:
			if (collect_name)
				name += ch;
			i++;
			break;
		}
	}

	if (class_name.size()) {
		static wstring wclass(L"class");
		strstk.push(wclass, class_name);
	}
}


void appendStack_CssSnippets(CWstrsStack &stack) {
	ZENABBREVLIST csssnippets[] = {
		{L"@i", L"@import url(|);"},
		{L"@m", L"@media print {\n\t|\n}"},
		{L"@f", L"@font-face {\n\tfont-family:|;\n\tsrc:url(|);\n}"},
		{L"!", L"!important"},
		{L"exp", L"expression()"},
		{L"pos", L"position:|;"},
		{L"pos:s", L"position:static;"},
		{L"pos:a", L"position:absolute;"},
		{L"pos:r", L"position:relative;"},
		{L"pos:f", L"position:fixed;"},
		{L"t", L"top:|;"},
		{L"t:a", L"top:auto;"},
		{L"r", L"right:|;"},
		{L"r:a", L"right:auto;"},
		{L"b", L"bottom:|;"},
		{L"b:a", L"bottom:auto;"},
		{L"brad", L"-webkit-border-radius: ${1:radius};\n-moz-border-radius: $1;\n-ms-border-radius: $1;\nborder-radius: $1;"},
		{L"bsha", L"-webkit-box-shadow: ${1:hoff} ${2:voff} ${3:blur} ${4:rgba(0,0,0,0.5)};\n-moz-box-shadow: $1 $2 $3 $4;\n-ms-box-shadow: $1 $2 $3 $4;\nbox-shadow: $1 $2 $3 $4;"},
		{L"l", L"left:|;"},
		{L"l:a", L"left:auto;"},
		{L"z", L"z-index:|;"},
		{L"z:a", L"z-index:auto;"},
		{L"fl", L"float:|;"},
		{L"fl:n", L"float:none;"},
		{L"fl:l", L"float:left;"},
		{L"fl:r", L"float:right;"},
		{L"cl", L"clear:|;"},
		{L"cl:n", L"clear:none;"},
		{L"cl:l", L"clear:left;"},
		{L"cl:r", L"clear:right;"},
		{L"cl:b", L"clear:both;"},
		{L"d", L"display:|;"},
		{L"d:n", L"display:none;"},
		{L"d:b", L"display:block;"},
		{L"d:i", L"display:inline;"},
		{L"d:ib", L"display:inline-block;"},
		{L"d:li", L"display:list-item;"},
		{L"d:ri", L"display:run-in;"},
		{L"d:cp", L"display:compact;"},
		{L"d:tb", L"display:table;"},
		{L"d:itb", L"display:inline-table;"},
		{L"d:tbcp", L"display:table-caption;"},
		{L"d:tbcl", L"display:table-column;"},
		{L"d:tbclg", L"display:table-column-group;"},
		{L"d:tbhg", L"display:table-header-group;"},
		{L"d:tbfg", L"display:table-footer-group;"},
		{L"d:tbr", L"display:table-row;"},
		{L"d:tbrg", L"display:table-row-group;"},
		{L"d:tbc", L"display:table-cell;"},
		{L"d:rb", L"display:ruby;"},
		{L"d:rbb", L"display:ruby-base;"},
		{L"d:rbbg", L"display:ruby-base-group;"},
		{L"d:rbt", L"display:ruby-text;"},
		{L"d:rbtg", L"display:ruby-text-group;"},
		{L"v", L"visibility:|;"},
		{L"v:v", L"visibility:visible;"},
		{L"v:h", L"visibility:hidden;"},
		{L"v:c", L"visibility:collapse;"},
		{L"ov", L"overflow:|;"},
		{L"ov:v", L"overflow:visible;"},
		{L"ov:h", L"overflow:hidden;"},
		{L"ov:s", L"overflow:scroll;"},
		{L"ov:a", L"overflow:auto;"},
		{L"ovx", L"overflow-x:|;"},
		{L"ovx:v", L"overflow-x:visible;"},
		{L"ovx:h", L"overflow-x:hidden;"},
		{L"ovx:s", L"overflow-x:scroll;"},
		{L"ovx:a", L"overflow-x:auto;"},
		{L"ovy", L"overflow-y:|;"},
		{L"ovy:v", L"overflow-y:visible;"},
		{L"ovy:h", L"overflow-y:hidden;"},
		{L"ovy:s", L"overflow-y:scroll;"},
		{L"ovy:a", L"overflow-y:auto;"},
		{L"ovs", L"overflow-style:|;"},
		{L"ovs:a", L"overflow-style:auto;"},
		{L"ovs:s", L"overflow-style:scrollbar;"},
		{L"ovs:p", L"overflow-style:panner;"},
		{L"ovs:m", L"overflow-style:move;"},
		{L"ovs:mq", L"overflow-style:marquee;"},
		{L"zoo", L"zoom:1;"},
		{L"cp", L"clip:|;"},
		{L"cp:a", L"clip:auto;"},
		{L"cp:r", L"clip:rect(|);"},
		{L"bxz", L"box-sizing:|;"},
		{L"bxz:cb", L"box-sizing:content-box;"},
		{L"bxz:bb", L"box-sizing:border-box;"},
		{L"bxsh", L"box-shadow:|;"},
		{L"bxsh:n", L"box-shadow:none;"},
		{L"bxsh:w", L"-webkit-box-shadow:0 0 0 #000;"},
		{L"bxsh:m", L"-moz-box-shadow:0 0 0 0 #000;"},
		{L"m", L"margin:|;"},
		{L"m:a", L"margin:auto;"},
		{L"m:0", L"margin:0;"},
		{L"m:2", L"margin:0 0;"},
		{L"m:3", L"margin:0 0 0;"},
		{L"m:4", L"margin:0 0 0 0;"},
		{L"mt", L"margin-top:|;"},
		{L"mt:a", L"margin-top:auto;"},
		{L"mr", L"margin-right:|;"},
		{L"mr:a", L"margin-right:auto;"},
		{L"mb", L"margin-bottom:|;"},
		{L"mb:a", L"margin-bottom:auto;"},
		{L"ml", L"margin-left:|;"},
		{L"ml:a", L"margin-left:auto;"},
		{L"p", L"padding:|;"},
		{L"p:0", L"padding:0;"},
		{L"p:2", L"padding:0 0;"},
		{L"p:3", L"padding:0 0 0;"},
		{L"p:4", L"padding:0 0 0 0;"},
		{L"pt", L"padding-top:|;"},
		{L"pr", L"padding-right:|;"},
		{L"pb", L"padding-bottom:|;"},
		{L"pl", L"padding-left:|;"},
		{L"w", L"width:|;"},
		{L"w:a", L"width:auto;"},
		{L"h", L"height:|;"},
		{L"h:a", L"height:auto;"},
		{L"maw", L"max-width:|;"},
		{L"maw:n", L"max-width:none;"},
		{L"mah", L"max-height:|;"},
		{L"mah:n", L"max-height:none;"},
		{L"miw", L"min-width:|;"},
		{L"mih", L"min-height:|;"},
		{L"o", L"outline:|;"},
		{L"o:n", L"outline:none;"},
		{L"oo", L"outline-offset:|;"},
		{L"ow", L"outline-width:|;"},
		{L"os", L"outline-style:|;"},
		{L"oc", L"outline-color:#000;"},
		{L"oc:i", L"outline-color:invert;"},
		{L"bd", L"border:|;"},
		{L"bd+", L"border:1px solid #000;"},
		{L"bd:n", L"border:none;"},
		{L"bdbk", L"border-break:|;"},
		{L"bdbk:c", L"border-break:close;"},
		{L"bdcl", L"border-collapse:|;"},
		{L"bdcl:c", L"border-collapse:collapse;"},
		{L"bdcl:s", L"border-collapse:separate;"},
		{L"bdc", L"border-color:#000;"},
		{L"bdi", L"border-image:url(|);"},
		{L"bdi:n", L"border-image:none;"},
		{L"bdi:w", L"-webkit-border-image:url(|) 0 0 0 0 stretch stretch;"},
		{L"bdi:m", L"-moz-border-image:url(|) 0 0 0 0 stretch stretch;"},
		{L"bdti", L"border-top-image:url(|);"},
		{L"bdti:n", L"border-top-image:none;"},
		{L"bdri", L"border-right-image:url(|);"},
		{L"bdri:n", L"border-right-image:none;"},
		{L"bdbi", L"border-bottom-image:url(|);"},
		{L"bdbi:n", L"border-bottom-image:none;"},
		{L"bdli", L"border-left-image:url(|);"},
		{L"bdli:n", L"border-left-image:none;"},
		{L"bdci", L"border-corner-image:url(|);"},
		{L"bdci:n", L"border-corner-image:none;"},
		{L"bdci:c", L"border-corner-image:continue;"},
		{L"bdtli", L"border-top-left-image:url(|);"},
		{L"bdtli:n", L"border-top-left-image:none;"},
		{L"bdtli:c", L"border-top-left-image:continue;"},
		{L"bdtri", L"border-top-right-image:url(|);"},
		{L"bdtri:n", L"border-top-right-image:none;"},
		{L"bdtri:c", L"border-top-right-image:continue;"},
		{L"bdbri", L"border-bottom-right-image:url(|);"},
		{L"bdbri:n", L"border-bottom-right-image:none;"},
		{L"bdbri:c", L"border-bottom-right-image:continue;"},
		{L"bdbli", L"border-bottom-left-image:url(|);"},
		{L"bdbli:n", L"border-bottom-left-image:none;"},
		{L"bdbli:c", L"border-bottom-left-image:continue;"},
		{L"bdf", L"border-fit:|;"},
		{L"bdf:c", L"border-fit:clip;"},
		{L"bdf:r", L"border-fit:repeat;"},
		{L"bdf:sc", L"border-fit:scale;"},
		{L"bdf:st", L"border-fit:stretch;"},
		{L"bdf:ow", L"border-fit:overwrite;"},
		{L"bdf:of", L"border-fit:overflow;"},
		{L"bdf:sp", L"border-fit:space;"},
		{L"bdl", L"border-length:|;"},
		{L"bdl:a", L"border-length:auto;"},
		{L"bdsp", L"border-spacing:|;"},
		{L"bds", L"border-style:|;"},
		{L"bds:n", L"border-style:none;"},
		{L"bds:h", L"border-style:hidden;"},
		{L"bds:dt", L"border-style:dotted;"},
		{L"bds:ds", L"border-style:dashed;"},
		{L"bds:s", L"border-style:solid;"},
		{L"bds:db", L"border-style:double;"},
		{L"bds:dtds", L"border-style:dot-dash;"},
		{L"bds:dtdtds", L"border-style:dot-dot-dash;"},
		{L"bds:w", L"border-style:wave;"},
		{L"bds:g", L"border-style:groove;"},
		{L"bds:r", L"border-style:ridge;"},
		{L"bds:i", L"border-style:inset;"},
		{L"bds:o", L"border-style:outset;"},
		{L"bdw", L"border-width:|;"},
		{L"bdt", L"border-top:|;"},
		{L"bdt+", L"border-top:1px solid #000;"},
		{L"bdt:n", L"border-top:none;"},
		{L"bdtw", L"border-top-width:|;"},
		{L"bdts", L"border-top-style:|;"},
		{L"bdts:n", L"border-top-style:none;"},
		{L"bdtc", L"border-top-color:#000;"},
		{L"bdr", L"border-right:|;"},
		{L"bdr+", L"border-right:1px solid #000;"},
		{L"bdr:n", L"border-right:none;"},
		{L"bdrw", L"border-right-width:|;"},
		{L"bdrs", L"border-right-style:|;"},
		{L"bdrs:n", L"border-right-style:none;"},
		{L"bdrc", L"border-right-color:#000;"},
		{L"bdb", L"border-bottom:|;"},
		{L"bdb+", L"border-bottom:1px solid #000;"},
		{L"bdb:n", L"border-bottom:none;"},
		{L"bdbw", L"border-bottom-width:|;"},
		{L"bdbs", L"border-bottom-style:|;"},
		{L"bdbs:n", L"border-bottom-style:none;"},
		{L"bdbc", L"border-bottom-color:#000;"},
		{L"bdl", L"border-left:|;"},
		{L"bdl+", L"border-left:1px solid #000;"},
		{L"bdl:n", L"border-left:none;"},
		{L"bdlw", L"border-left-width:|;"},
		{L"bdls", L"border-left-style:|;"},
		{L"bdls:n", L"border-left-style:none;"},
		{L"bdlc", L"border-left-color:#000;"},
		{L"bdrs", L"border-radius:|;"},
		{L"bdtrrs", L"border-top-right-radius:|;"},
		{L"bdtlrs", L"border-top-left-radius:|;"},
		{L"bdbrrs", L"border-bottom-right-radius:|;"},
		{L"bdblrs", L"border-bottom-left-radius:|;"},
		{L"bg", L"background:|;"},
		{L"bg+", L"background:#FFF url(|) 0 0 no-repeat;"},
		{L"bg:n", L"background:none;"},
		{L"bg:ie", L"filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(src='${1:x}.png',sizingMethod='${2:crop}');"},
		{L"bgc", L"background-color:#FFF;"},
		{L"bgi", L"background-image:url(|);"},
		{L"bgi:n", L"background-image:none;"},
		{L"bgr", L"background-repeat:|;"},
		{L"bgr:n", L"background-repeat:no-repeat;"},
		{L"bgr:x", L"background-repeat:repeat-x;"},
		{L"bgr:y", L"background-repeat:repeat-y;"},
		{L"bga", L"background-attachment:|;"},
		{L"bga:f", L"background-attachment:fixed;"},
		{L"bga:s", L"background-attachment:scroll;"},
		{L"bgp", L"background-position:0 0;"},
		{L"bgpx", L"background-position-x:|;"},
		{L"bgpy", L"background-position-y:|;"},
		{L"bgbk", L"background-break:|;"},
		{L"bgbk:bb", L"background-break:bounding-box;"},
		{L"bgbk:eb", L"background-break:each-box;"},
		{L"bgbk:c", L"background-break:continuous;"},
		{L"bgcp", L"background-clip:|;"},
		{L"bgcp:bb", L"background-clip:border-box;"},
		{L"bgcp:pb", L"background-clip:padding-box;"},
		{L"bgcp:cb", L"background-clip:content-box;"},
		{L"bgcp:nc", L"background-clip:no-clip;"},
		{L"bgo", L"background-origin:|;"},
		{L"bgo:pb", L"background-origin:padding-box;"},
		{L"bgo:bb", L"background-origin:border-box;"},
		{L"bgo:cb", L"background-origin:content-box;"},
		{L"bgz", L"background-size:|;"},
		{L"bgz:a", L"background-size:auto;"},
		{L"bgz:ct", L"background-size:contain;"},
		{L"bgz:cv", L"background-size:cover;"},
		{L"c", L"color:#000;"},
		{L"tbl", L"table-layout:|;"},
		{L"tbl:a", L"table-layout:auto;"},
		{L"tbl:f", L"table-layout:fixed;"},
		{L"cps", L"caption-side:|;"},
		{L"cps:t", L"caption-side:top;"},
		{L"cps:b", L"caption-side:bottom;"},
		{L"ec", L"empty-cells:|;"},
		{L"ec:s", L"empty-cells:show;"},
		{L"ec:h", L"empty-cells:hide;"},
		{L"lis", L"list-style:|;"},
		{L"lis:n", L"list-style:none;"},
		{L"lisp", L"list-style-position:|;"},
		{L"lisp:i", L"list-style-position:inside;"},
		{L"lisp:o", L"list-style-position:outside;"},
		{L"list", L"list-style-type:|;"},
		{L"list:n", L"list-style-type:none;"},
		{L"list:d", L"list-style-type:disc;"},
		{L"list:c", L"list-style-type:circle;"},
		{L"list:s", L"list-style-type:square;"},
		{L"list:dc", L"list-style-type:decimal;"},
		{L"list:dclz", L"list-style-type:decimal-leading-zero;"},
		{L"list:lr", L"list-style-type:lower-roman;"},
		{L"list:ur", L"list-style-type:upper-roman;"},
		{L"lisi", L"list-style-image:|;"},
		{L"lisi:n", L"list-style-image:none;"},
		{L"q", L"quotes:|;"},
		{L"q:n", L"quotes:none;"},
		{L"q:ru", L"quotes:'\x00AB' '\x00BB' '\x201E' '\x201C';"},
		{L"q:en", L"quotes:'\x201C' '\x201D' '\x2018' '\x2019';"},
		{L"ct", L"content:|;"},
		{L"ct:n", L"content:normal;"},
		{L"ct:oq", L"content:open-quote;"},
		{L"ct:noq", L"content:no-open-quote;"},
		{L"ct:cq", L"content:close-quote;"},
		{L"ct:ncq", L"content:no-close-quote;"},
		{L"ct:a", L"content:attr(|);"},
		{L"ct:c", L"content:counter(|);"},
		{L"ct:cs", L"content:counters(|);"},
		{L"coi", L"counter-increment:|;"},
		{L"cor", L"counter-reset:|;"},
		{L"va", L"vertical-align:|;"},
		{L"va:sup", L"vertical-align:super;"},
		{L"va:t", L"vertical-align:top;"},
		{L"va:tt", L"vertical-align:text-top;"},
		{L"va:m", L"vertical-align:middle;"},
		{L"va:bl", L"vertical-align:baseline;"},
		{L"va:b", L"vertical-align:bottom;"},
		{L"va:tb", L"vertical-align:text-bottom;"},
		{L"va:sub", L"vertical-align:sub;"},
		{L"ta", L"text-align:|;"},
		{L"ta:l", L"text-align:left;"},
		{L"ta:c", L"text-align:center;"},
		{L"ta:r", L"text-align:right;"},
		{L"tal", L"text-align-last:|;"},
		{L"tal:a", L"text-align-last:auto;"},
		{L"tal:l", L"text-align-last:left;"},
		{L"tal:c", L"text-align-last:center;"},
		{L"tal:r", L"text-align-last:right;"},
		{L"td", L"text-decoration:|;"},
		{L"td:n", L"text-decoration:none;"},
		{L"td:u", L"text-decoration:underline;"},
		{L"td:o", L"text-decoration:overline;"},
		{L"td:l", L"text-decoration:line-through;"},
		{L"te", L"text-emphasis:|;"},
		{L"te:n", L"text-emphasis:none;"},
		{L"te:ac", L"text-emphasis:accent;"},
		{L"te:dt", L"text-emphasis:dot;"},
		{L"te:c", L"text-emphasis:circle;"},
		{L"te:ds", L"text-emphasis:disc;"},
		{L"te:b", L"text-emphasis:before;"},
		{L"te:a", L"text-emphasis:after;"},
		{L"th", L"text-height:|;"},
		{L"th:a", L"text-height:auto;"},
		{L"th:f", L"text-height:font-size;"},
		{L"th:t", L"text-height:text-size;"},
		{L"th:m", L"text-height:max-size;"},
		{L"ti", L"text-indent:|;"},
		{L"ti:-", L"text-indent:-9999px;"},
		{L"tj", L"text-justify:|;"},
		{L"tj:a", L"text-justify:auto;"},
		{L"tj:iw", L"text-justify:inter-word;"},
		{L"tj:ii", L"text-justify:inter-ideograph;"},
		{L"tj:ic", L"text-justify:inter-cluster;"},
		{L"tj:d", L"text-justify:distribute;"},
		{L"tj:k", L"text-justify:kashida;"},
		{L"tj:t", L"text-justify:tibetan;"},
		{L"to", L"text-outline:|;"},
		{L"to+", L"text-outline:0 0 #000;"},
		{L"to:n", L"text-outline:none;"},
		{L"tr", L"text-replace:|;"},
		{L"tr:n", L"text-replace:none;"},
		{L"tt", L"text-transform:|;"},
		{L"tt:n", L"text-transform:none;"},
		{L"tt:c", L"text-transform:capitalize;"},
		{L"tt:u", L"text-transform:uppercase;"},
		{L"tt:l", L"text-transform:lowercase;"},
		{L"tw", L"text-wrap:|;"},
		{L"tw:n", L"text-wrap:normal;"},
		{L"tw:no", L"text-wrap:none;"},
		{L"tw:u", L"text-wrap:unrestricted;"},
		{L"tw:s", L"text-wrap:suppress;"},
		{L"tsh", L"text-shadow:|;"},
		{L"tsh+", L"text-shadow:0 0 0 #000;"},
		{L"tsh:n", L"text-shadow:none;"},
		{L"lh", L"line-height:|;"},
		{L"whs", L"white-space:|;"},
		{L"whs:n", L"white-space:normal;"},
		{L"whs:p", L"white-space:pre;"},
		{L"whs:nw", L"white-space:nowrap;"},
		{L"whs:pw", L"white-space:pre-wrap;"},
		{L"whs:pl", L"white-space:pre-line;"},
		{L"whsc", L"white-space-collapse:|;"},
		{L"whsc:n", L"white-space-collapse:normal;"},
		{L"whsc:k", L"white-space-collapse:keep-all;"},
		{L"whsc:l", L"white-space-collapse:loose;"},
		{L"whsc:bs", L"white-space-collapse:break-strict;"},
		{L"whsc:ba", L"white-space-collapse:break-all;"},
		{L"wob", L"word-break:|;"},
		{L"wob:n", L"word-break:normal;"},
		{L"wob:k", L"word-break:keep-all;"},
		{L"wob:l", L"word-break:loose;"},
		{L"wob:bs", L"word-break:break-strict;"},
		{L"wob:ba", L"word-break:break-all;"},
		{L"wos", L"word-spacing:|;"},
		{L"wow", L"word-wrap:|;"},
		{L"wow:nm", L"word-wrap:normal;"},
		{L"wow:n", L"word-wrap:none;"},
		{L"wow:u", L"word-wrap:unrestricted;"},
		{L"wow:s", L"word-wrap:suppress;"},
		{L"lts", L"letter-spacing:|;"},
		{L"f", L"font:|;"},
		{L"f+", L"font:1em Arial,sans-serif;"},
		{L"fw", L"font-weight:|;"},
		{L"fw:n", L"font-weight:normal;"},
		{L"fw:b", L"font-weight:bold;"},
		{L"fw:br", L"font-weight:bolder;"},
		{L"fw:lr", L"font-weight:lighter;"},
		{L"fs", L"font-style:|;"},
		{L"fs:n", L"font-style:normal;"},
		{L"fs:i", L"font-style:italic;"},
		{L"fs:o", L"font-style:oblique;"},
		{L"fv", L"font-variant:|;"},
		{L"fv:n", L"font-variant:normal;"},
		{L"fv:sc", L"font-variant:small-caps;"},
		{L"fz", L"font-size:|;"},
		{L"fza", L"font-size-adjust:|;"},
		{L"fza:n", L"font-size-adjust:none;"},
		{L"ff", L"font-family:|;"},
		{L"ff:s", L"font-family:serif;"},
		{L"ff:ss", L"font-family:sans-serif;"},
		{L"ff:c", L"font-family:cursive;"},
		{L"ff:f", L"font-family:fantasy;"},
		{L"ff:m", L"font-family:monospace;"},
		{L"fef", L"font-effect:|;"},
		{L"fef:n", L"font-effect:none;"},
		{L"fef:eg", L"font-effect:engrave;"},
		{L"fef:eb", L"font-effect:emboss;"},
		{L"fef:o", L"font-effect:outline;"},
		{L"fem", L"font-emphasize:|;"},
		{L"femp", L"font-emphasize-position:|;"},
		{L"femp:b", L"font-emphasize-position:before;"},
		{L"femp:a", L"font-emphasize-position:after;"},
		{L"fems", L"font-emphasize-style:|;"},
		{L"fems:n", L"font-emphasize-style:none;"},
		{L"fems:ac", L"font-emphasize-style:accent;"},
		{L"fems:dt", L"font-emphasize-style:dot;"},
		{L"fems:c", L"font-emphasize-style:circle;"},
		{L"fems:ds", L"font-emphasize-style:disc;"},
		{L"fsm", L"font-smooth:|;"},
		{L"fsm:a", L"font-smooth:auto;"},
		{L"fsm:n", L"font-smooth:never;"},
		{L"fsm:aw", L"font-smooth:always;"},
		{L"fst", L"font-stretch:|;"},
		{L"fst:n", L"font-stretch:normal;"},
		{L"fst:uc", L"font-stretch:ultra-condensed;"},
		{L"fst:ec", L"font-stretch:extra-condensed;"},
		{L"fst:c", L"font-stretch:condensed;"},
		{L"fst:sc", L"font-stretch:semi-condensed;"},
		{L"fst:se", L"font-stretch:semi-expanded;"},
		{L"fst:e", L"font-stretch:expanded;"},
		{L"fst:ee", L"font-stretch:extra-expanded;"},
		{L"fst:ue", L"font-stretch:ultra-expanded;"},
		{L"op", L"opacity:|;"},
		{L"op:ie", L"filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=100);"},
		{L"op:ms", L"-ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=100)';"},
		{L"rz", L"resize:|;"},
		{L"rz:n", L"resize:none;"},
		{L"rz:b", L"resize:both;"},
		{L"rz:h", L"resize:horizontal;"},
		{L"rz:v", L"resize:vertical;"},
		{L"cur", L"cursor:|;"},
		{L"cur:a", L"cursor:auto;"},
		{L"cur:d", L"cursor:default;"},
		{L"cur:c", L"cursor:crosshair;"},
		{L"cur:ha", L"cursor:hand;"},
		{L"cur:he", L"cursor:help;"},
		{L"cur:m", L"cursor:move;"},
		{L"cur:p", L"cursor:pointer;"},
		{L"cur:t", L"cursor:text;"},
		{L"pgbb", L"page-break-before:|;"},
		{L"pgbb:au", L"page-break-before:auto;"},
		{L"pgbb:al", L"page-break-before:always;"},
		{L"pgbb:l", L"page-break-before:left;"},
		{L"pgbb:r", L"page-break-before:right;"},
		{L"pgbi", L"page-break-inside:|;"},
		{L"pgbi:au", L"page-break-inside:auto;"},
		{L"pgbi:av", L"page-break-inside:avoid;"},
		{L"pgba", L"page-break-after:|;"},
		{L"pgba:au", L"page-break-after:auto;"},
		{L"pgba:al", L"page-break-after:always;"},
		{L"pgba:l", L"page-break-after:left;"},
		{L"pgba:r", L"page-break-after:right;"},
		{L"orp", L"orphans:|;"},
		{L"wid", L"widows:|;"}
	};

	for (size_t i=0; i<sizeof(csssnippets)/sizeof(ZENABBREVLIST); i++) {
		stack.push(csssnippets[i].pabbr, csssnippets[i].pexpa);
	}
}

void appendStack_HtmlSnippets(CWstrsStack &stack) {
	ZENABBREVLIST htmlsnippets[] = {
		{L"cc:ie6", L"<!--[if lte IE 6]>\n\t${child}|\n<![endif]-->"},
		{L"cc:ie", L"<!--[if IE]>\n\t${child}|\n<![endif]-->"},
		{L"cc:noie", L"<!--[if !IE]><!-->\n\t${child}|\n<!--<![endif]-->"},
		{L"html:4t", L"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n"
			L"<html lang=\"${lang}\">\n"
			L"<head>\n"
			L"	<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\">\n"
			L"	<title></title>\n"
			L"</head>\n"
			L"<body>\n\t${child}|\n</body>\n"
			L"</html>"},

		{L"html:4s", L"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n"
			L"<html lang=\"${lang}\">\n"
			L"<head>\n"
			L"	<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\">\n"
			L"	<title></title>\n"
			L"</head>\n"
			L"<body>\n\t${child}|\n</body>\n"
			L"</html>"},

		{L"html:xt", L"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
			L"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"${lang}\">\n"
			L"<head>\n"
			L"	<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\" />\n"
			L"	<title></title>\n"
			L"</head>\n"
			L"<body>\n\t${child}|\n</body>\n"
			L"</html>"},

		{L"html:xs", L"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n"
			L"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"${lang}\">\n"
			L"<head>\n"
			L"	<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\" />\n"
			L"	<title></title>\n"
			L"</head>\n"
			L"<body>\n\t${child}|\n</body>\n"
			L"</html>"},

		{L"html:xxs", L"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n"
			L"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"${lang}\">\n"
			L"<head>\n"
			L"	<meta http-equiv=\"Content-Type\" content=\"text/html;charset=${charset}\" />\n"
			L"	<title></title>\n"
			L"</head>\n"
			L"<body>\n\t${child}|\n</body>\n"
			L"</html>"},
		
		{L"html:5", L"<!DOCTYPE HTML>\n"
			L"<html lang=\"${locale}\">\n"
			L"<head>\n"
			L"	<meta charset=\"${charset}\">\n"
			L"	<title></title>\n"
			L"</head>\n"
			L"<body>\n\t${child}|\n</body>\n"
			L"</html>"}
	};

	for (size_t i=0; i<sizeof(htmlsnippets)/sizeof(ZENABBREVLIST); i++) {
		stack.push(htmlsnippets[i].pabbr, htmlsnippets[i].pexpa);
	}
}

void appendStack_HtmlAbbreviations(CWstrsStack &stack) {
	ZENABBREVLIST htmlabbreviations[] = {
		{L"a", L"<a href=\"\">"},
		{L"a:link", L"<a href=\"http://|\">"},
		{L"a:mail", L"<a href=\"mailto:|\">"},
		{L"abbr", L"<abbr title=\"\">"},
		{L"acronym", L"<acronym title=\"\">"},
		{L"base", L"<base href=\"\" />"},
		{L"bdo", L"<bdo dir=\"\">"},
		{L"bdo:r", L"<bdo dir=\"rtl\">"},
		{L"bdo:l", L"<bdo dir=\"ltr\">"},
		{L"link:css", L"<link rel=\"stylesheet\" type=\"text/css\" href=\"${1:style}.css\" media=\"all\" />"},
		{L"link:print", L"<link rel=\"stylesheet\" type=\"text/css\" href=\"|print.css\" media=\"print\" />"},
		{L"link:favicon", L"<link rel=\"shortcut icon\" type=\"image/x-icon\" href=\"|favicon.ico\" />"},
		{L"link:touch", L"<link rel=\"apple-touch-icon\" href=\"|favicon.png\" />"},
		{L"link:rss", L"<link rel=\"alternate\" type=\"application/rss+xml\" title=\"RSS\" href=\"|rss.xml\" />"},
		{L"link:atom", L"<link rel=\"alternate\" type=\"application/atom+xml\" title=\"Atom\" href=\"atom.xml\" />"},
		{L"meta:utf", L"<meta http-equiv=\"Content-Type\" content=\"text/html;charset=UTF-8\" />"},
		{L"meta:win", L"<meta http-equiv=\"Content-Type\" content=\"text/html;charset=windows-1251\" />"},
		{L"meta:compat", L"<meta http-equiv=\"X-UA-Compatible\" content=\"IE=7\" />"},
		{L"style", L"<style type=\"text/css\">"},
		{L"script", L"<script type=\"text/javascript\">"},
		{L"script:src", L"<script type=\"text/javascript\" src=\"\">"},
		{L"img", L"<img src=\"\" alt=\"\" />"},
		{L"iframe", L"<iframe src=\"\" frameborder=\"0\">"},
		{L"embed", L"<embed src=\"\" type=\"\" />"},
		{L"object", L"<object data=\"\" type=\"\">"},
		{L"param", L"<param name=\"\" value=\"\" />"},
		{L"map", L"<map name=\"\">"},
		{L"area", L"<area shape=\"\" coords=\"\" href=\"\" alt=\"\" />"},
		{L"area:d", L"<area shape=\"default\" href=\"\" alt=\"\" />"},
		{L"area:c", L"<area shape=\"circle\" coords=\"\" href=\"\" alt=\"\" />"},
		{L"area:r", L"<area shape=\"rect\" coords=\"\" href=\"\" alt=\"\" />"},
		{L"area:p", L"<area shape=\"poly\" coords=\"\" href=\"\" alt=\"\" />"},
		{L"link", L"<link rel=\"stylesheet\" href=\"\" />"},
		{L"form", L"<form action=\"\">"},
		{L"form:get", L"<form action=\"\" method=\"get\">"},
		{L"form:post", L"<form action=\"\" method=\"post\">"},
		{L"label", L"<label for=\"\">"},
		{L"input", L"<input type=\"\" />"},
		{L"input:hidden", L"<input type=\"hidden\" name=\"\" />"},
		{L"input:h", L"<input type=\"hidden\" name=\"\" />"},
		{L"input:text", L"<input type=\"text\" name=\"\" id=\"\" />"},
		{L"input:t", L"<input type=\"text\" name=\"\" id=\"\" />"},
		{L"input:search", L"<input type=\"search\" name=\"\" id=\"\" />"},
		{L"input:email", L"<input type=\"email\" name=\"\" id=\"\" />"},
		{L"input:url", L"<input type=\"url\" name=\"\" id=\"\" />"},
		{L"input:password", L"<input type=\"password\" name=\"\" id=\"\" />"},
		{L"input:p", L"<input type=\"password\" name=\"\" id=\"\" />"},
		{L"input:datetime", L"<input type=\"datetime\" name=\"\" id=\"\" />"},
		{L"input:date", L"<input type=\"date\" name=\"\" id=\"\" />"},
		{L"input:datetime-local", L"<input type=\"datetime-local\" name=\"\" id=\"\" />"},
		{L"input:month", L"<input type=\"month\" name=\"\" id=\"\" />"},
		{L"input:week", L"<input type=\"week\" name=\"\" id=\"\" />"},
		{L"input:time", L"<input type=\"time\" name=\"\" id=\"\" />"},
		{L"input:number", L"<input type=\"number\" name=\"\" id=\"\" />"},
		{L"input:color", L"<input type=\"color\" name=\"\" id=\"\" />"},
		{L"input:checkbox", L"<input type=\"checkbox\" name=\"\" id=\"\" />"},
		{L"input:c", L"<input type=\"checkbox\" name=\"\" id=\"\" />"},
		{L"input:radio", L"<input type=\"radio\" name=\"\" id=\"\" />"},
		{L"input:r", L"<input type=\"radio\" name=\"\" id=\"\" />"},
		{L"input:range", L"<input type=\"range\" name=\"\" id=\"\" />"},
		{L"input:file", L"<input type=\"file\" name=\"\" id=\"\" />"},
		{L"input:f", L"<input type=\"file\" name=\"\" id=\"\" />"},
		{L"input:submit", L"<input type=\"submit\" value=\"\" />"},
		{L"input:s", L"<input type=\"submit\" value=\"\" />"},
		{L"input:image", L"<input type=\"image\" src=\"\" alt=\"\" />"},
		{L"input:i", L"<input type=\"image\" src=\"\" alt=\"\" />"},
		{L"input:reset", L"<input type=\"reset\" value=\"\" />"},
		{L"input:button", L"<input type=\"button\" value=\"\" />"},
		{L"input:b", L"<input type=\"button\" value=\"\" />"},
		{L"select", L"<select name=\"\" id=\"\"></select>"},
		{L"option", L"<option value=\"\"></option>"},
		{L"textarea", L"<textarea name=\"\" id=\"\" cols=\"30\" rows=\"10\">"},
		{L"menu:context", L"<menu type=\"context\">"},
		{L"menu:c", L"<menu type=\"context\">"},
		{L"menu:toolbar", L"<menu type=\"toolbar\">"},
		{L"menu:t", L"<menu type=\"toolbar\">"},
		{L"video", L"<video src=\"\">"},
		{L"audio", L"<audio src=\"\">"},
		{L"html:xml", L"<html xmlns=\"http://www.w3.org/1999/xhtml\">"},
		{L"bq", L"<blockquote>"},
		{L"acr", L"<acronym>"},
		{L"fig", L"<figure>"},
		{L"ifr", L"<iframe>"},
		{L"emb", L"<embed>"},
		{L"obj", L"<object>"},
		{L"src", L"<source>"},
		{L"cap", L"<caption>"},
		{L"colg", L"<colgroup>"},
		{L"fst", L"<fieldset>"},
		{L"btn", L"<button>"},
		{L"optg", L"<optgroup>"},
		{L"opt", L"<option>"},
		{L"tarea", L"<textarea>"},
		{L"leg", L"<legend>"},
		{L"sect", L"<section>"},
		{L"art", L"<article>"},
		{L"hdr", L"<header>"},
		{L"ftr", L"<footer>"},
		{L"adr", L"<address>"},
		{L"dlg", L"<dialog>"},
		{L"str", L"<strong>"},
		{L"prog", L"<progress>"},
		{L"fset", L"<fieldset>"},
		{L"datag", L"<datagrid>"},
		{L"datal", L"<datalist>"},
		{L"kg", L"<keygen>"},
		{L"out", L"<output>"},
		{L"det", L"<details>"},
		{L"cmd", L"<command>"},

		// expandos
		{L"ol+", L"ol>li"},
		{L"ul+", L"ul>li"},
		{L"dl+", L"dl>dt+dd"},
		{L"map+", L"map>area"},
		{L"table+", L"table>tr>td"},
		{L"colgroup+", L"colgroup>col"},
		{L"colg+", L"colgroup>col"},
		{L"tr+", L"tr>td"},
		{L"select+", L"select>option"},
		{L"optgroup+", L"optgroup>option"},
		{L"optg+", L"optgroup>option"}
	};

	for (size_t i=0; i<sizeof(htmlabbreviations)/sizeof(ZENABBREVLIST); i++) {
		stack.push(htmlabbreviations[i].pabbr, htmlabbreviations[i].pexpa);
	}
}

void appendStack_XslAbbreviations(CWstrsStack &stack) {
	ZENABBREVLIST xslabbreviations[] = {
		{L"tm", L"<xsl:template match=\"\" mode=\"\">"},
		{L"tmatch", L"tm"},
		{L"tn", L"<xsl:template name=\"\">"},
		{L"tname", L"tn"},
		{L"xsl:when", L"<xsl:when test=\"\">"},
		{L"wh", L"xsl:when"},
		{L"var", L"<xsl:variable name=\"\">"},
		{L"vare", L"<xsl:variable name=\"\" select=\"\"/>"},
		{L"if", L"<xsl:if test=\"\">"},
		{L"call", L"<xsl:call-template name=\"\"/>"},
		{L"attr", L"<xsl:attribute name=\"\">"},
		{L"wp", L"<xsl:with-param name=\"\" select=\"\"/>"},
		{L"par", L"<xsl:param name=\"\" select=\"\"/>"},
		{L"val", L"<xsl:value-of select=\"\"/>"},
		{L"co", L"<xsl:copy-of select=\"\"/>"},
		{L"each", L"<xsl:for-each select=\"\">"},
		{L"for", L"each"},
		{L"ap", L"<xsl:apply-templates select=\"\" mode=\"\"/>"},

		//expandos
		{L"choose+", L"xsl:choose>xsl:when+xsl:otherwise"}
	};

	for (size_t i=0; i<sizeof(xslabbreviations)/sizeof(ZENABBREVLIST); i++) {
		stack.push(xslabbreviations[i].pabbr, xslabbreviations[i].pexpa);
	}
}


void AppendResourceChainBase(CWstrsStack &chain, ZenSyntaxEnum syntax, ZenName::Enum name) {
	switch (syntax) {
	case zen_syntax_html:
		if (name == ZenName::abbreviation) {
			appendStack_HtmlAbbreviations(chain);
		} else if (name == ZenName::snippet) {
			appendStack_HtmlSnippets(chain);
		}
		break;

	case zen_syntax_css:
		if (name == ZenName::snippet) {
			appendStack_CssSnippets(chain);
		}
		break;

	case zen_syntax_xsl:
		if (name == ZenName::abbreviation) {
			appendStack_XslAbbreviations(chain);
		}
		break;

	default:
		break;
	}
}

void createResourceChain(CWstrsStack &chain, ZenSyntaxEnum syntax, ZenName::Enum name) {
	// L812
	chain.removeAll();

	/*
	if (name in resource)
		result.push(resource[name]);
	*/

	// CSS: Snippet only
	// HTML: Snippet and Abbreviation
	// XSL: Abbreviation only

	AppendResourceChainBase(chain, syntax, name);

	// get inheritance definition
	// in case of user-defined vocabulary, resource dependency
	// may be defined in system vocabulary only, so we have to correctly
	// handle this case

	/*
	var chain_source;
	if (resource && 'extends' in resource)
		chain_source = resource;
	else if (vocabulary == VOC_USER && syntax in system_settings
		&& 'extends' in system_settings[syntax] )
		chain_source = system_settings[syntax];

	if (chain_source) {
		if (!isParsed(chain_source['extends'])) {
			var ar = chain_source['extends'].split(',');
			for (var i = 0; i < ar.length; i++)
				ar[i] = trim(ar[i]);
			chain_source['extends'] = ar;
			setParsed(chain_source['extends']);
		}

		// find resource in ancestors
		for (var i = 0; i < chain_source['extends'].length; i++) {
			var type = chain_source['extends'][i];
			if (voc[type] && voc[type][name])
				result.push(voc[type][name]);
		}
	}
	*/

	// [result of analysis]
	// 1. 'extends' has 'xml', 'xsl' only & the content is html
	// 2. don't care VOC_USER
	// 3. setParsed(), isParsed() are actually NOTHING
	// 4. chain_source['extends'] is just html. (told in #1)
	// 5. in this version, extends is very simple, so I don't care. ;)

	if (syntax == zen_syntax_xml || syntax == zen_syntax_xsl) {
		AppendResourceChainBase(chain, zen_syntax_html, name);
	}
}

bool getAbbreviation(ZenSyntaxEnum type, wstring name, CParsedItem &parseditem) {
	if (getParsedItem(type, ZenName::abbreviation, name, parseditem)) {
		return true;
	} else {
		for (int i = 0, il = (int)name.size(); i < il; i++) {
			if (name[i]==L'-') name[i]=L':';
		}
		return (getParsedItem(type, ZenName::abbreviation, name, parseditem));
	}
}

bool getSnippet(ZenSyntaxEnum type, wstring name, wstring &snippet) {
	CParsedItem parseditem;

	if (getParsedItem(type, ZenName::snippet, name, parseditem)) {
		snippet = parseditem.getValue();
		return true;
	} else {
		for (int i = 0, il = (int)name.size(); i < il; i++) {
			if (name[i]==L'-') name[i]=L':';
		}

		bool ret = (getParsedItem(type, ZenName::snippet, name, parseditem));
		snippet = parseditem.getValue();

		return ret;
	}
}

bool getParsedItem(ZenSyntaxEnum syntax, ZenName::Enum name, wstring &item, CParsedItem &parseditem) {
	// L876

	CWstrsStack chain;
	createResourceChain(chain, syntax, name);
	parseditem.cleanUp();

	//return type of ParseAbbreviation() is...
	//Abbreviation -> {TYPE_*, key, value} class
	//Snippet -> value
	//so, ZenName::snippet is additionally needed
	for (int i = 0, il = chain.getLen(); i < il; i++) {
		wstring res1, res2;
		chain.getVal(i, res1, res2);
		if (res1 == item) {
			if (name==ZenName::abbreviation) {
				parseditem.parseAbbreviation(res1, res2);
			} else {
				parseditem.setValueOnly(res2);
			}
			return true;
		}
	}
	return false;
}


bool getVariable(wstring &var, wstring &value) {
	ZENABBREVLIST variables[] = {
		{L"lang", ZENCODINGLANG},
		{L"locale", ZENCODINGLOCALE},
		{L"charset", ZENCODINGCHRSET},

		/** Inner element indentation */
		{L"indentation", L"\t"},

		// newline variables, useful for wrapping
		{L"newline", L"\n"},
		{L"nl", L"\n"}
	};

	for (size_t i=0; i<sizeof(variables)/sizeof(ZENABBREVLIST); i++) {
		if (var.compare((const wchar_t*)(variables[i].pabbr)) == 0) {
			value = variables[i].pexpa;
			return true;
		}
	}

	value = L"";
	return false;
}

bool getVariable(wchar_t *var, wstring &value) {
	wstring wsvar(var);
	return getVariable(wsvar, value);
}

wstring getNewLine() {
	static wstring var = L"newline";
	wstring newline;
	getVariable(var, newline);

	return newline;
}

wstring getIndentation() {
	wstring value;
	getVariable((wchar_t*)L"indentation", value);

	return value;
}


// parseIntoTree()
////////////////////////////////////////////////////////////

void dumpToken(CTreeNode * context, wstring &token) {
	if (token.size())
		context->setAbbreviation(token);

	token = L"";
}

CTreeNode * parse(wstring abbr) {
	// L1516

	CTreeNode * root = new CTreeNode();
	CTreeNode * context = root->addChild(NULL);
	
	int i = 0;
	int il = (int)abbr.size();
	int text_lvl = 0;
	int attr_lvl = 0;
	int group_lvl = 0;
	CTreeNodestack group_stack;
	group_stack.push(root);

	wchar_t ch, prev_ch;
	wstring token;


	while (i<il) {
		ch = abbr[i];
		prev_ch = i?abbr[i-1]:0;
		switch (ch) {
		case L'{':
			if (!attr_lvl)
				text_lvl++;
			token += ch;
			break;

		case L'}':
			if (!attr_lvl)
				text_lvl--;
			token += ch;
			break;

		case L'[':
			if (!text_lvl)
				attr_lvl++;
			token += ch;
			break;

		case L']':
			if (!text_lvl)
				attr_lvl--;
			token += ch;
			break;

		case L'(':
			if (!text_lvl && !attr_lvl) {
				// beginning of the new group
				dumpToken(context, token);

				if (prev_ch != L'+' && prev_ch != L'>') {
					// previous char is not an operator, assume it's
					// a sibling
					context = context->getParent()->addChild(NULL);
				}

				group_stack.push(context);
				context = context->addChild(NULL);
			} else {
				token += ch;
			}
			break;

		case L')':
			if (!text_lvl && !attr_lvl) {
				// end of the group, pop stack
				dumpToken(context, token);

				group_stack.pop(context);

				if ((i<il-1) && abbr[i+1]==L'*') {
					// group multiplication
					wstring group_mul;
					wchar_t n_ch;
					int iGroup_mul=1;

					for (int j = i + 2; j < il; j++) {
						n_ch = abbr[j];
						if (ISNUMERIC(n_ch))
							group_mul += n_ch;
						else
							break;
					}

					i+=((int)group_mul.size()+1);
					iGroup_mul = parseInt(group_mul);
					while (1<iGroup_mul--) {
						CTreeNode * newcontext = context->copy();
						context->getParent()->addChild(newcontext);
					}
				}
			} else {
				token += ch;
			}
			break;

		case L'+':	// sibling operator
			if (!text_lvl && !attr_lvl && i!=il-1 /* expando? */ ) {
				dumpToken(context, token);
				context = context->getParent()->addChild(NULL);
			} else {
				token += ch;
			}
			break;

		case L'>':	// child operator
			if (!text_lvl && !attr_lvl) {
				dumpToken(context, token);
				context = context->addChild(NULL);
			} else {
				token += ch;
			}
			break;

		default:
			token += ch;
			break;
		}

		i++;
	}
	// put the final token
	dumpToken(context, token);

	return (root->optimizeTree());
}

bool isSnippet(wstring name, ZenSyntaxEnum type) {
	wstring snippet;
	return getSnippet(type, name, snippet);
}

CSnippetOrTag *transformTreeNode(CTreeNode *node, ZenSyntaxEnum type) {
	if ((type != zen_syntax_css) && (type != zen_syntax_xml) && (type != zen_syntax_xsl))
		type = zen_syntax_html;
	if (node->isEmpty()) return NULL;

	CSnippetOrTag *ret = new CSnippetOrTag();
	if (isSnippet(node->getName(), type)) {
		ret->InitSnippet(node, type);
	} else {
		ret->InitTag(node, type);
	}
	return ret;
}

void processParsedNode(CTreeNode * node, ZenSyntaxEnum type, CSnippetOrTag *parent) {
	CSnippetOrTag *t_node = transformTreeNode(node, type);
	parent->addChild(t_node);

	// set repeating element to the topmost node
	CSnippetOrTag *root = parent;
	while (root->getParent())
		root = root->getParent();

	root->setLast(t_node);
	if (t_node->getRepeatByLines())
		root->setMultiplyElem(t_node);

	// process child groups
	for (int j = 0, jl = node->getChildCount(); j < jl; j++) {
		processParsedNode(node->getNstChild(j), type, t_node);
	}
}

void replaceExpandos(CTreeNode * node, ZenSyntaxEnum type) {
	for (int i=0, il = node->getChildCount(); i < il; i++) {
		CTreeNode *n = node->getNstChild(i);
		if ((!n->isEmpty()) && (!n->isTextNode()) && (n->getName().find(L'+') != wstring::npos)) {
			// it's expando
			CParsedItem a;
			if (getAbbreviation(type, n->getName(), a)) {
				node->setNstChild(i, parse(a.getValue()));
			}
		}
		replaceExpandos(node->getNstChild(i), type);
	}
}

CTreeNode * preprocessParsedTree(CTreeNode * tree, ZenSyntaxEnum type) {
	replaceExpandos(tree, type);
	return tree->optimizeTree();
}

CSnippetOrTag *parseIntoTree(wstring abbr, ZenSyntaxEnum type) {
	//L2714
	if ((type != zen_syntax_css) && (type != zen_syntax_xml) && (type != zen_syntax_xsl))
		type = zen_syntax_html;

	// remove filters from abbreviation
	wstring filter_list;
	moveFilterFromAbbr(abbr, filter_list);

	// try to parse abbreviation
	CTreeNode *abbr_tree = parse(trimWstr(abbr));
	// exception: "InvalidAbbreviation"
	if (!abbr_tree) return NULL;
	CSnippetOrTag * tree_root = new CSnippetOrTag();
	tree_root->InitTag(NULL, type);

	abbr_tree = preprocessParsedTree(abbr_tree, type);

	// then recursively expand each group item
	for (int i = 0, il = abbr_tree->getChildCount(); i < il; i++) {
		processParsedNode(abbr_tree->getNstChild(i), type, tree_root);
	}

	tree_root->setFilters(filter_list);
	return tree_root;
}


// rolloutTree()
////////////////////////////////////////////////////////////

bool isItemInCollectionEmpty(wstring name) {
	wchar_t *EmptyElements[] = {
		L"area",	L"base",	L"basefont",	L"br",
		L"col",		L"frame",	L"hr",			L"img",
		L"input",	L"isindex",	L"link",		L"meta",
		L"param",	L"embed",	L"keygen",		L"command"
	};

	for (size_t i=0; i<sizeof(EmptyElements)/sizeof(EmptyElements[0]); i++) {
		if (name.compare(EmptyElements[i]) == 0) return true;
	}

	return false;
}

bool isItemInCollectionInlineLevel(wstring name) {
	wchar_t *InlineElements[] = {
		L"a",		L"abbr",	L"acronym",		L"applet",
		L"b",		L"basefont",L"bdo",			L"big",
		L"br",		L"button",	L"cite",		L"code",
		L"del",		L"dfn",		L"em",			L"font",
		L"i",		L"iframe",	L"img",			L"input",
		L"ins",		L"kbd",		L"label",		L"map",
		L"object",	L"q",		L"s",			L"samp",
		L"select",	L"small",	L"span",		L"strike",
		L"strong",	L"sub",		L"sup",			L"textarea",
		L"tt",		L"u",		L"var"
	};

	for (size_t i=0; i<sizeof(InlineElements)/sizeof(InlineElements[0]); i++) {
		if (name.compare(InlineElements[i]) == 0) return true;
	}

	return false;
}

bool isItemInCollectionBlockLevel(wstring name) {
	wchar_t *BlockElements[] = {
		L"address",	L"applet",	L"blockquote",	L"button",
		L"center",	L"dd",		L"del",			L"dir",
		L"div",		L"dl",		L"dt",			L"fieldset",
		L"form",	L"frameset",L"hr",			L"iframe",
		L"ins",		L"isindex",	L"li",			L"link",
		L"map",		L"menu",	L"noframes",	L"noscript",
		L"object",	L"ol",		L"p",			L"pre",
		L"script",	L"table",	L"tbody",		L"td",
		L"tfoot",	L"th",		L"thead",		L"tr",
		L"ul",		L"h1",		L"h2",			L"h3",
		L"h4",		L"h5",		L"h6"
	};

	for (size_t i=0; i<sizeof(BlockElements)/sizeof(BlockElements[0]); i++) {
		if (name.compare(BlockElements[i]) == 0) return true;
	}

	return false;
}

bool hasOutputPlaceholder0(wstring text) {
	for (int i = 0, il = (int)text.size(); i < il; i++) {
		wchar_t ch = text[i];
		if (ch == L'\\') { // escaped char
			i++;
			continue;
		} else if (ch == L'$' && (i+1<il) && text[i + 1] == L'#') {
			return true;
		}
	}

	return false;
}

void splitByLines(wstring text, bool remove_empty, vector <wstring> &lines) {
	int pos=0;
	lines.clear();
	int slen = (int)text.size();

	while (pos<slen) {
		int NLpos, NLlen;
		wstring oneline;

		if (findNewLine(text, pos, NLpos, NLlen)) {
			oneline = text.substr(pos, NLpos-pos);
			pos = NLpos+NLlen;
		} else {
			oneline = text.substr(pos);
			pos = slen;
		}

		if (remove_empty) {
			oneline = trimWstr(oneline);
			if (oneline.size()) {
				lines.push_back(oneline);
			}
		} else {
			lines.push_back(oneline);
		}
	}
}

CZenNode *rolloutTree(CSnippetOrTag *tree, CZenNode *parent) {
	if (!parent) {
		parent = new CZenNode(tree);
	}

	int how_many = 1;
	wstring tag_content_str = L"";
	vector <wstring> tag_content_array;
	bool bTagContentIsArray;

	if (tree->getChildCount()) {
		for (int i = 0, il = tree->getChildCount(); i < il; i++) {
			/** @type {Tag} */
			CSnippetOrTag *child = tree->getNstChild(i);
			how_many = child->getCount();

			if (child->getRepeatByLines()) {
				// it's a repeating element
				splitByLines(child->getPasteContent(), true, tag_content_array);
				bTagContentIsArray = true;
				if ((how_many = (int)tag_content_array.size())<1)
					how_many = 1;
			} else {
				tag_content_str = child->getPasteContent();
				bTagContentIsArray = false;
			}

			for (int j = 0; j < how_many; j++) {
				CZenNode *tag = new CZenNode(child);
				parent->addChild(tag);
				tag->setCounter(j+1);

				if (child->getChildCount())
					rolloutTree(child, tag);

				if (bTagContentIsArray) {
					if (tag_content_array.size()) {
						wstring text = tag_content_array[j];
						tag->pasteContent(trimWstr(text));
					}
				} else {
					if (tag_content_str.size()) {
						wstring text = tag_content_str;
						tag->pasteContent(trimWstr(text));
					}
				}
			}
		}
	}

	return parent;
}


// applyFilters()
////////////////////////////////////////////////////////////

CProfile::CProfile(ZenProfileEnum profile) {
	switch (profile) {
	case zen_profile_xhtml:
		this->setXhtmlProfile();
		break;

	case zen_profile_html:
		this->setHtmlProfile();
		break;

	case zen_profile_xml:
		this->setXmlProfile();
		break;

	case zen_profile_plain:
	default:
		this->setPlainProfile();
		break;
	}

	this->child_token = L"${child}";
	this->placeholder = L"%s";
	this->tabstops = 0;
}

CProfile::~CProfile() {}

void CProfile::setDefaultProfile() {
	this->tag_case_is_lower = true;
	this->attr_case_is_lower = true;
	this->attr_quotes_is_double = true;
	this->tag_nl = ZenTagOnNewLine::DECIDE;
	this->place_cursor = true;
	this->indent = true;
	this->inline_break = 3;
	this->self_closing_tag = ZenSelfClosingTag::XHTML;
	this->filters = L"";
}

void CProfile::setXhtmlProfile() {
	this->setDefaultProfile();
}

void CProfile::setHtmlProfile() {
	this->setDefaultProfile();
	this->self_closing_tag = ZenSelfClosingTag::FALSE;
}

void CProfile::setXmlProfile() {
	this->setDefaultProfile();
	this->self_closing_tag = ZenSelfClosingTag::TRUE;
	this->tag_nl = ZenTagOnNewLine::TRUE;
}

void CProfile::setPlainProfile() {
	this->setDefaultProfile();
	this->tag_nl = ZenTagOnNewLine::FALSE;
	this->indent = false;
	this->place_cursor = false;
}

//This comment is copied from original source
/**
 * Need to add line break before element
 */
bool CProfile::shouldBreakLine(CZenNode *node) {
	if (!this->inline_break)
		return false;

	// find toppest non-inline sibling
	while (node->getPreviousSibling() && node->getPreviousSibling()->isInline())
		node = node->getPreviousSibling();

	if (!node->isInline())
		return false;

	// calculate how many inline siblings we have
	int node_count = 1;
	while (node = node->getNextSibling()) {
		// node.type can never be 'text'...
		// if (node.type == 'text' || !node.isInline())
		if (!node->isInline())
			node_count = 0;
		else if (node->isInline())
			node_count++;
	}

	return node_count >= this->inline_break;
}

//This comment is copied from original source
/**
 * Need to add newline because <code>item</code> has too many inline children
 */
bool CProfile::shouldBreakChild (CZenNode *node) {
	// we need to test only one child element, because
	// hasBlockChildren() method will do the rest
	return (node->getChildCount() && this->shouldBreakLine(node->getNstChild(0)));
}


CZenNode *CProfile::runFilter(CZenNode *tree, ZenFilters::Enum filter) {
	switch (filter) {
	case ZenFilters::c:
		return this->runFilter_c(tree); break;
	case ZenFilters::css:
		return this->runFilter_css(tree); break;
	case ZenFilters::e:
		return this->runFilter_e(tree); break;
	case ZenFilters::fc:
		return this->runFilter_fc(tree); break;
	case ZenFilters::_format:
		return this->runFilter__format(tree); break;
	case ZenFilters::haml:
		return this->runFilter_haml(tree); break;
	case ZenFilters::html:
		return this->runFilter_html(tree); break;
	case ZenFilters::s:
		return this->runFilter_s(tree); break;
	case ZenFilters::t:
		return this->runFilter_t(tree); break;
	case ZenFilters::xsl:
		return this->runFilter_xsl(tree); break;
	default:
		return tree;
	}
}

//This comment is copied from original source
/**
 * Comment important tags (with 'id' and 'class' attributes)
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_c(CZenNode *tree) {
	// L6798
	if (this->tag_nl == ZenTagOnNewLine::FALSE)
		return tree;

	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);

		if (item->isBlock())
			item->addComments(i);

		this->runFilter_c(item);
	}

	return tree;
}

//This comment is copied from original source
/**
 * Process CSS properties: replaces snippets, augumented with ! char, with
 * <em>!important</em> suffix
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_css(CZenNode *tree) {
	// L6824
	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);

		// CSS properties are always snippets
		if (!item->isTag() && iRegExp07(item->getRealName())) {
			wstring start = item->getStart();
			int slen = (int)start.size();
			bool semicolon;

			if (slen && start[slen-1]==L';') {
				semicolon = true;
				start = start.substr(0, slen-1);
			} else {
				semicolon = false;
			}
			if (semicolon) {
				start += L" !important;";
			} else {
				start += L" !important";
			}

			item->setStart(start);
		}

		this->runFilter_css(item);
	}

	return tree;
}

//This comment is copied from original source
/**
 * Filter for escaping unsafe XML characters: <, >, &
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_e(CZenNode *tree) {
	// L6859
	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);

		item->setStart(escapeChars(item->getStart()));
		item->setEnd(escapeChars(item->getEnd()));

		this->runFilter_e(item);
	}

	return tree;
}

//This comment is copied from original source
/**
 * Format CSS properties: add space after property name:
 * padding:0; -> padding: 0;
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_fc(CZenNode *tree) {
	// L6881
	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);

		// CSS properties are always snippets
		if (!item->isTag()) {
			wstring start = item->getStart();
			for (int j = 0, jl = (int)start.size(); j < jl; j++) {
				int filteredlen;
				int replacelen = iRegExp10(start, filteredlen, j);
				if (replacelen>=0) {
					wstring filteredstr = start.substr(j, filteredlen);
					filteredstr += L" ";

					item->setStart(start.replace(i, replacelen, filteredstr));

					break;
				}
			}
		}

		this->runFilter_fc(item);
	}

	return tree;
}

CZenNode *CProfile::processSnippet__format(CZenNode *item, int level) {
	// L6984
	wstring data = item->getSource()->getValue();
	if (data.size() == 0) {
		return this->processTag__format(item, level);
	}

	item->setStart(this->placeholder);
	item->setEnd(this->placeholder);

	wstring padding = (item->getParent())
			? (item->getParent()->getPadding())
			: repeatString(getIndentation(), level);

	if (item->isVeryFirstChild()) {
		item->setStart(getNewLine() + padding + item->getStart());
	}

	// adjust item formatting according to last line of <code>start</code> property
	vector <wstring> parts;
	splitWstr(data, child_token, parts);

	vector <wstring> lines;
	splitByLines(parts[0], false, lines);

	wstring padding_delta = getIndentation();

	if (lines.size() > 1) {
		wstring m;
		wstring oneline = lines[lines.size()-1];
		for (int i=0, il=(int)oneline.size(); i<il; i++) {
			if (ISSPACE(oneline[i])) {
				m+=oneline[i];
			} else {
				break;
			}
		}

		if (m.size()) {
			padding_delta = m;
		}
	}

	item->setPadding(padding + padding_delta);

	return item;
}

CZenNode *CProfile::processTag__format(CZenNode *item, int level) {
	// L7023
	if (item->getName().size() == 0)
		// looks like it's a root element
		return item;

	item->setStart(this->placeholder);
	item->setEnd(this->placeholder);

	bool is_unary = (item->isUnary() && !(item->getChildCount()));

	// formatting output
	if (this->tag_nl != ZenTagOnNewLine::FALSE) {
		wstring padding = (item->getParent())
				? item->getParent()->getPadding()
				: repeatString(getIndentation(), level);
		bool force_nl = (this->tag_nl == ZenTagOnNewLine::TRUE);
		bool should_break = this->shouldBreakLine(item);

		// formatting block-level elements
		// item.type can never be 'text'
		// if (item.type != 'text') {
		{
			if (( (item->isBlock() || should_break) && item->getParent()) || force_nl) {
				// snippet children should take different formatting
				if (!item->getParent() || (item->getParent()->isTag() && !item->isVeryFirstChild()))
					item->setStart(getNewLine() + padding + item->getStart());

				if (item->hasBlockChildren() || this->shouldBreakChild(item) || (force_nl && !is_unary))
					item->setEnd(getNewLine() + padding + item->getEnd());

				if (item->hasTagsInContent() || (force_nl && !item->hasChildren() && !is_unary))
					item->setStart(item->getStart() + getNewLine() + padding + getIndentation());
			} else if (item->isInline() && item->hasBlockSibling() && !item->isVeryFirstChild()) {
				item->setStart(getNewLine() + padding + item->getStart());
			} else if (item->isInline() && item->hasBlockChildren()) {
				item->setEnd(getNewLine() + padding + item->getEnd());
			}

			item->setPadding(padding + getIndentation());
		}
	}

	return item;
}

//This comment is copied from original source
/**
 * Generic formatting filter: creates proper indentation for each tree node,
 * placing "%s" placeholder where the actual output should be. You can use
 * this filter to preformat tree and then replace %s placeholder to whatever you
 * need. This filter should't be called directly from editor as a part
 * of abbreviation.
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter__format(CZenNode *tree, int level) {
	// L7072
	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);
		item = (item->isTag())
			? this->processTag__format(item, level)
			: this->processSnippet__format(item, level);

		if (item->getContent().size())
			item->setContent(padString(item->getContent(), item->getPadding()));

		runFilter__format(item, level + 1);
	}

	return tree;
}

wstring CProfile::makeAttributesString_haml(CZenNode *tag) {
	// L7107
	wstring attrs;
	wstring attr_quote = this->attr_quotes_is_double ? L"\"":L"\'";
	wstring cursor = this->place_cursor ? global.getCaretPlaceholder() : L"";
	wstring attr_name;
	int i;
	wstring a1, a2;

	// use short notation for ID and CLASS attributes
	for (i = 0; i < tag->getAttributes()->getLen(); i++) {
		tag->getAttributes()->getVal(i, a1, a2);
		makeLowerWstr(a1);
		if (a1 == L"id") attrs += (L"#" + a2.size()?a2:cursor);
		else if (a1 == L"class") attrs += (L"." + a2.size()?a2:cursor);
	}

	vector <wstring> other_attrs;

	// process other attributes
	for (i = 0; i < tag->getAttributes()->getLen(); i++) {
		tag->getAttributes()->getVal(i, a1, a2);
		wstring attr_name_lower = a1;
		makeLowerWstr(attr_name_lower);
		if (attr_name_lower != L"id" && attr_name_lower != L"class") {
			attr_name = a1;
			if (this->attr_case_is_lower) {
				makeLowerWstr(attr_name);
			} else {
				makeUpperWstr(attr_name);
			}
			other_attrs.push_back(L":" + attr_name + L" => " + attr_quote + (a2.size()?a2:cursor) + attr_quote);
		}
	}

	if (other_attrs.size()) {
		wstring attr_added = joinWstr(other_attrs, L", ");

		attrs += (L"{" + attr_added + L"}");
	}

	return attrs;
}

CZenNode *CProfile::processSnippet_haml(CZenNode *item, int level) {
	// L7153
	wstring data = item->getSource()->getValue();
	if (data.size() == 0) {
		// snippet wasn't found, process it as tag
		return this->processTag_haml(item, level);
	}

	vector <wstring> parts;
	splitWstr(data, child_token, parts);
	wstring start;
	if (parts.size()>0) start = parts[0];
	wstring end;
	if (parts.size()>1) start = parts[1];
	wstring padding;
	if (item->getParent()) padding = item->getParent()->getPadding();

	item->setStart(replaceWstr(item->getStart(), L"%s", padString(start, padding)));
	item->setEnd(replaceWstr(item->getEnd(), L"%s", padString(end, padding)));

	// replace variables ID and CLASS
	item->setStart(item->replaceVariables(item->getStart()));
	item->setEnd(item->replaceVariables(item->getEnd()));

	return item;
}

CZenNode *CProfile::processTag_haml(CZenNode *item, int level) {
	// L7196
	if (item->getName().size() == 0)
		// looks like it's a root element
		return item;

	wstring attrs = this->makeAttributesString_haml(item);
	wstring content;
	wstring cursor = this->place_cursor ? global.getCaretPlaceholder() : L"";
	wstring self_closing;
	bool is_unary = (item->isUnary() && !item->getChildCount());
	wstring start;
	wstring end;

	if (this->self_closing_tag==ZenSelfClosingTag::TRUE && is_unary)
		self_closing = L"/";

	// define tag name
	wstring tag_name = L"%" + item->getName();
	if (this->tag_case_is_lower) {
		makeLowerWstr(tag_name);
	} else {
		makeUpperWstr(tag_name);
	}

	if (((tag_name == L"%div") || (tag_name == L"%DIV")) && attrs.size() && (attrs.find(L"{") == wstring::npos))
		// omit div tag
		tag_name = L"";

	item->setEnd(L"");
	start = tag_name + attrs + self_closing;

	wstring placeholder = L"%s";
	// We can't just replace placeholder with new value because
	// JavaScript will treat double $ character as a single one, assuming
	// we're using RegExp literal.
	size_t pos = item->getStart().find(placeholder);
	wstring tempstart = item->getStart();
	if (pos == wstring::npos) {
		if (tempstart.size()) tempstart = tempstart.erase(0, 1);
		item->setStart(start + tempstart);
	} else {
		item->setStart(tempstart.substr(0, pos) + start + tempstart.substr(pos + placeholder.size()));
	}

	if (!item->getChildCount() && !is_unary)
		item->setStart(item->getStart() + cursor);

	return item;
}

//This comment is copied from original source
/**
 * Filter that produces HAML tree
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_haml(CZenNode *tree, int level) {
	// L7240

	if (!level)
		// preformat tree
		tree = this->runFilter__format(tree);

	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);
		item = (item->isTag())
			? this->processTag_haml(item, level)
			: this->processSnippet_haml(item, level);

		// replace counters
		int counter = item->getCounterForNode();
		item->setStart(unescapeText(replaceCounter(item->getStart(), counter)));
		item->setEnd(unescapeText(replaceCounter(item->getEnd(), counter)));

		runFilter_haml(item, level + 1);
	}

	return tree;
}

wstring CProfile::processStringCase_html(wstring val, bool attr_case_is_lower) {
	if (attr_case_is_lower) {
		makeLowerWstr(val);
	} else {
		makeUpperWstr(val);
	}

	return val;
}

wstring CProfile::makeAttributesString_html(CZenNode *tag) {
	// L7297

	// make attribute string
	wstring attrs;
	wstring attr_quote = this->attr_quotes_is_double ? L"\"":L"\'";
	wstring cursor = this->place_cursor ? global.getCaretPlaceholder() : L"";
	wstring attr_name;
	int i;
	wstring a1, a2;

	for (i = 0; i < tag->getAttributes()->getLen(); i++) {
		tag->getAttributes()->getVal(i, a1, a2);
		attr_name = processStringCase_html(a1, this->attr_case_is_lower);
		attrs += (L" " + attr_name + L"=" + attr_quote + (a2.size()?a2:cursor) + attr_quote);
	}

	return attrs;
}

CZenNode *CProfile::processSnippet_html(CZenNode *item, int level) {
	// L7319
	wstring data = item->getSource()->getValue();
	if (data.size() == 0) {
		// snippet wasn't found, process it as tag
		return this->processTag_html(item, level);
	}

	vector <wstring> parts;
	splitWstr(data, child_token, parts);
	wstring start;
	if (parts.size()>0) start = parts[0];
	wstring end;
	if (parts.size()>1) end = parts[1];
	wstring padding;
	if (item->getParent()) padding = item->getParent()->getPadding();

	item->setStart(replaceWstr(item->getStart(), L"%s", padString(start, padding)));
	item->setEnd(replaceWstr(item->getEnd(), L"%s", padString(end, padding)));

	// replace variables ID and CLASS
	item->setStart(item->replaceVariables(item->getStart()));
	item->setEnd(item->replaceVariables(item->getEnd()));

	return item;
}

CZenNode *CProfile::processTag_html(CZenNode *item, int level) {
	// L7363

	if (item->getName().size() == 0)
		// looks like it's a root element
		return item;

	wstring attrs = this->makeAttributesString_html(item);
	wstring content;
	wstring cursor = this->place_cursor ? global.getCaretPlaceholder() : L"";
	wstring self_closing;
	bool is_unary = (item->isUnary() && !item->getChildCount());
	wstring start;
	wstring end;

	if (this->self_closing_tag==ZenSelfClosingTag::XHTML) {
		self_closing = L" /";
	} else if (this->self_closing_tag==ZenSelfClosingTag::TRUE) {
		self_closing = L"/";
	}

	// define opening and closing tags
	// item.type can never be 'text'
	// if (item.type != 'text') {
	{
		wstring tag_name = processStringCase_html(item->getName(), this->tag_case_is_lower);
		if (is_unary) {
			start = (L"<" + tag_name + attrs + self_closing + L">");
			item->setEnd(L"");
		} else {
			start = (L"<" + tag_name + attrs + L">");
			end = (L"</" + tag_name + L">");
		}
	}

	wstring placeholder = L"%s";
	// We can't just replace placeholder with new value because
	// JavaScript will treat double $ character as a single one, assuming
	// we're using RegExp literal.
	size_t pos = item->getStart().find(placeholder);
	wstring tempstart = item->getStart();
	if (pos == wstring::npos) {
		if (tempstart.size()) tempstart = tempstart.erase(0, 1);
		item->setStart(start + tempstart);
	} else {
		item->setStart(tempstart.substr(0, pos) + start + tempstart.substr(pos + placeholder.size()));
	}

	pos = item->getEnd().find(placeholder);
	wstring tempend = item->getEnd();
	if (pos == wstring::npos) {
		if (tempend.size()) tempend = tempend.erase(0, 1);
		item->setEnd(end + tempend);
	} else {
		item->setEnd(tempend.substr(0, pos) + end + tempend.substr(pos + placeholder.size()));
	}

	if (!item->getChildCount() && !is_unary && (item->getContent().find(cursor) == wstring::npos))
		item->setStart(item->getStart() + cursor);

	return item;
}

//This comment is copied from original source
/**
 * Filter that produces HTML tree
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_html(CZenNode *tree, int level) {
	// L7415

	if (!level) {
		tree = this->runFilter__format(tree);
		this->tabstops = 0;
	}

	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */

		CZenNode *item = tree->getNstChild(i);
		item = (item->isTag())
			? this->processTag_html(item, level)
			: this->processSnippet_html(item, level);

		// replace counters
		int counter = item->getCounterForNode();
		item->setStart(unescapeText(replaceCounter(item->getStart(), counter)));
		item->setEnd(unescapeText(replaceCounter(item->getEnd(), counter)));
		item->setContent(unescapeText(replaceCounter(item->getContent(), counter)));

		this->tabstops += (item->upgradeTabstops(this->tabstops) + 1);

		runFilter_html(item, level + 1);
	}

	return tree;
}

//This comment is copied from original source
/**
 * Output abbreviation on a single line (i.e. no line breaks)
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_s(CZenNode *tree) {
	// L7451

	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);
		if (item->isTag()) {
			// remove padding from item
			item->setStart(ltrimWstr(item->getStart()));
			item->setEnd(ltrimWstr(item->getEnd()));
		}

		// remove newlines
		item->setStart(removenewlines(item->getStart()));
		item->setEnd(removenewlines(item->getEnd()));
		item->setContent(removenewlines(item->getContent()));

		runFilter_s(item);
	}

	return tree;
}

//This comment is copied from original source
/**
 * Trim filter: removes characters at the beginning of the text
 *  content that indicates lists: numbers, #, *, -, etc.
 * @author Sergey Chikuyonok (serge.che@gmail.com)
 * @link http://chikuyonok.ru
 */
CZenNode *CProfile::runFilter_t(CZenNode *tree) {
	// L7483

	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);

		wstring content = item->getContent();
		if (content.size()) {
			int begin, len;
			len = iRegExp12(content, begin);
			if (len>0) {
				content.erase(begin, len);
				item->setContent(content);
			}
		}

		runFilter_t(item);
	}

	return tree;
}

CZenNode *CProfile::runFilter_xsl(CZenNode *tree) {
	// L7519

	for (int i = 0, il = tree->getChildCount(); i < il; i++) {
		/** @type {ZenNode} */
		CZenNode *item = tree->getNstChild(i);
		if (item->isTag() && item->getChildCount()) {
			wstring name = item->getName();
			makeLowerWstr(name);
			if ((name == L"xsl:variable") || (name == L"xsl:with-param")) {
				item->trimAttribute();
			}
		}

		runFilter_xsl(item);
	}

	return tree;
}

void addFilter(vector <ZenFilters::Enum> &filters, ZenFilters::Enum filter) {
	filters.push_back(filter);
}

void addFilter(vector <ZenFilters::Enum> &filters, wstring filter) {
	makeLowerWstr(filter);
	ZenFilters::Enum _filter;
	if (filter==L"c") _filter=ZenFilters::c;
	else if (filter==L"css") _filter=ZenFilters::css;
	else if (filter==L"e") _filter=ZenFilters::e;
	else if (filter==L"fc") _filter=ZenFilters::fc;
	else if (filter==L"_format") _filter=ZenFilters::_format;
	else if (filter==L"haml") _filter=ZenFilters::haml;
	else if (filter==L"html") _filter=ZenFilters::html;
	else if (filter==L"s") _filter=ZenFilters::s;
	else if (filter==L"t") _filter=ZenFilters::t;
	else if (filter==L"xsl") _filter=ZenFilters::xsl;
	else return;

	addFilter(filters, _filter);
}

wstring getDefaultFilters(ZenSyntaxEnum syntax) {
	wstring ret;
	switch (syntax) {
	case zen_syntax_css:
		ret = L"html,css";
		break;

	case zen_syntax_xsl:
		ret = L"html,xsl";
		break;

	case zen_syntax_haml:
		ret = L"haml";
		break;

	//case zen_syntax_html:
	//case zen_syntax_xml:
	default:
		ret = L"html";
		break;
	}

	return ret;
}

void splitFilterList(wstring &filterlist, vector <ZenFilters::Enum> &filters) {
	filters.clear();
	//pos1: |
	//pos2: ,
	size_t prev_pos=0, pos1, pos2, pos=0;

	pos1 = filterlist.find(L"|", pos);
	pos2 = filterlist.find(L",", pos);
	while ((pos1 != wstring::npos) || (pos2 != wstring::npos)) {
		if (pos1 == wstring::npos)	pos = pos2;
		else if (pos2 == wstring::npos)	pos = pos1;
		else if (pos1<pos2)	pos = pos1;
		else pos = pos2;

		wstring filter(filterlist.substr(prev_pos, pos));
		addFilter(filters, filter);

		prev_pos = ++pos;

		pos1 = filterlist.find(L"|", pos);
		pos2 = filterlist.find(L",", pos);
	}

	wstring filter(filterlist.substr(prev_pos));
	addFilter(filters, filter);
}

CZenNode *runFilters(CZenNode *tree, CProfile &profile, wstring &filters) {
	vector <ZenFilters::Enum> filter_list;
	splitFilterList(filters, filter_list);

	for (int i = 0, il = (int)filter_list.size(); i < il; i++) {
		tree = profile.runFilter(tree, filter_list[i]);
	}
	return tree;
}

CZenNode *applyFilters(CZenNode *tree, ZenSyntaxEnum syntax, ZenProfileEnum profile, wstring additional_filters) {
	CProfile _profile(profile);
	wstring _filters = _profile.getFilters();

	if (_filters.size()==0) {
		_filters = getDefaultFilters(syntax);
	}

	if (additional_filters.size()) {
		_filters += ((L"|")+additional_filters);
	}

	return runFilters(tree, _profile, _filters);
}


wstring padString(wstring text, int pad) {
	wstring pad_str = repeatString(getIndentation(), pad);

	return padString(text, pad_str);
}

wstring padString(wstring text, wstring pad) {
	if (!text.size()) return text;

	wstring result;

	vector <wstring> lines;
	splitByLines(text, false, lines);
	wstring nl = getNewLine();

	result += lines[0];
	for (int j = 1; j < (int)lines.size(); j++)
		result += (nl + pad + lines[j]);

	return result;
}

wstring escape_fn_4uTS(wchar_t ch) {
	wstring ret=L"\\";
	ret+=ch;
	return ret;
}

wstring tabstop_fn_4uTS(wstring num, int &max_num, int offset, wstring value) {
	int num_ = parseInt(num);
	if (num_ > max_num) max_num = num_;

	if (value.size()) {
		return L"${" + intToWstr(num_ + offset) + L":" + value + L"}";
	} else {
		return L"${" + intToWstr(num_ + offset);
	}
}

int nextWhileIsNumeric(wstring &text, int &il, int ix) {
	while (ix < il) if (!ISNUMERIC(text[ix++])) break;
	return ix-1;
}

bool braceCheck(wchar_t c, int &brace_count) {
	if (c == L'{') brace_count++;
	else if (c == L'}') brace_count--;
	return !!brace_count;
}

int nextWhileBraceCheck(wstring &text, int &il, int &brace_count, int ix) {
	while (ix < il) if (!braceCheck(text[ix++], brace_count)) break;
	return ix-1;
}

//This comment is copied from original source
/**
 * Process text that should be pasted into editor: clear escaped text and
 * handle tabstops
 * @param {String} text
 * @return {String}
 */
wstring processTextBeforePaste_4uTS(wstring text, int &offset, int &max_num) {
	//L3071

	int i = 0, il = (int)text.size(), start_ix, _i;
	wstring str_builder;

	while (i < il) {
		wchar_t ch = text[i];
		if (ch == L'\\' && i + 1 < il) {
			// handle escaped character
			str_builder += escape_fn_4uTS(text[i+1]);
			i += 2;
			continue;
		} else if (ch == L'$') {
			// looks like a tabstop
			wchar_t next_ch = (i+1<il)?text[i + 1]:0;
			_i = i;
			if (ISNUMERIC(next_ch)) {
				// $N placeholder
				start_ix = i + 1;
				i = nextWhileIsNumeric(text, il, start_ix);
				if (start_ix < i) {
					str_builder+= tabstop_fn_4uTS(text.substr(start_ix, i-start_ix), max_num, offset);
					continue;
				}
			} else if (next_ch == L'{') {
				// ${N:value} or ${N} placeholder
				int brace_count = 1;
				start_ix = i + 2;
				i = nextWhileIsNumeric(text, il, start_ix);

				if ((i > start_ix) && (i<il)) {
					if (text[i] == L'}') {
						str_builder += tabstop_fn_4uTS(text.substr(start_ix, i-start_ix), max_num, offset);
						i++; // handle closing brace
						continue;
					} else if (text[i] == L':') {
						int val_start = i + 2;
						i = nextWhileBraceCheck(text, il, brace_count, val_start);
						str_builder += tabstop_fn_4uTS(text.substr(start_ix, val_start-2-start_ix), max_num, offset, text.substr(val_start-1, i-(val_start-1)));
						i++; // handle closing brace
						continue;
					}
				}
			}
			i = _i;
		}

		// push current character to stack
		str_builder += ch;
		i++;
	}

	return str_builder;
}


// handleTabStops()
////////////////////////////////////////////////////////////

//This comment is copied from original source
/**
 * Process text that should be pasted into editor: clear escaped text and
 * handle tabstops
 * @param {String} text
 * @return {String}
 */
wstring tabstop_fn_4hTS(int i, wstring num, CWstrsStack &placeholders, size_t &caret_pos, int &selection_len, wstring val) {
	if (val.size()) placeholders.pushWithCheck(num, val);

	if (i < (int)caret_pos) {
		caret_pos = i;
		if (val.size())
			selection_len = (int)val.size();
	}

	wstring ret;
	placeholders.iFind(num, ret);

	return ret;
}

wstring processTextBeforePaste_4hTS(wstring text, CWstrsStack &placeholders, size_t &caret_pos, int &selection_len) {
	//L3071

	int i = 0, il = (int)text.size(), start_ix, _i;
	wstring str_builder;
	wstring nullwstr;

	while (i < il) {
		wchar_t ch = text[i];
		if (ch == L'\\' && i + 1 < il) {
			// handle escaped character
			str_builder += text[i+1];
			i += 2;
			continue;
		} else if (ch == L'$') {
			// looks like a tabstop
			wchar_t next_ch = (i+1<il)?text[i + 1]:0;
			_i = i;
			if (ISNUMERIC(next_ch)) {
				// $N placeholder
				start_ix = i + 1;
				i = nextWhileIsNumeric(text, il, start_ix);
				if (start_ix < i) {
					str_builder+= tabstop_fn_4hTS(_i, text.substr(start_ix, i-start_ix), placeholders, caret_pos, selection_len);
					continue;
				}
			} else if (next_ch == L'{') {
				// ${N:value} or ${N} placeholder
				int brace_count = 1;
				start_ix = i + 2;
				i = nextWhileIsNumeric(text, il, start_ix);

				if ((i > start_ix) && (i<il)) {
					if (text[i] == L'}') {
						str_builder += tabstop_fn_4hTS(_i, text.substr(start_ix, i-start_ix), placeholders, caret_pos, selection_len);
						i++; // handle closing brace
						continue;
					} else if (text[i] == L':') {
						int val_start = i + 2;
						i = nextWhileBraceCheck(text, il, brace_count, val_start);
						str_builder += tabstop_fn_4hTS(_i, text.substr(start_ix, val_start-2-start_ix), placeholders, caret_pos, selection_len, text.substr(val_start-1, i-(val_start-1)));
						i++; // handle closing brace
						continue;
					}
				}
			}
			i = _i;
		}

		// push current character to stack
		str_builder += ch;
		i++;
	}

	return str_builder;
}

wstring handleTabStops(wstring text) {
	int selection_len = 0;
	size_t caret_pos = text.find(global.getCaretPlaceholder());
	CWstrsStack placeholders;

	// find caret position
	if (caret_pos != wstring::npos) {
		vector <wstring> textsplitted;
		splitWstr(text, global.getCaretPlaceholder(), textsplitted);
		text = joinWstr(textsplitted);
	} else {
		caret_pos = text.size();
	}

	return processTextBeforePaste_4hTS(text, placeholders, caret_pos, selection_len);
}


////////////////////////////
// 4th block              //
//   : interface function //
////////////////////////////

#ifdef __cplusplus
extern "C" {
#endif
int iZenCodingConv(wchar_t *src, wchar_t *dst, int dstsize, enum ZenSyntaxEnum syntax, enum ZenProfileEnum profile, int bDblSpaceIndentation, int bConv2CRLF) {
	if (!dstsize || !src || !src[0] || !dst) return -1;

	CSnippetOrTag * parsed_tree = parseIntoTree(src, syntax);
	wstring zen;
	if (parsed_tree) {
		CZenNode * tree = rolloutTree(parsed_tree);
		applyFilters(tree, syntax, profile, parsed_tree->getFilters());
		zen = handleTabStops(replaceVariables(tree->toString()));

		delete tree;
		delete parsed_tree;
	}

	if (bDblSpaceIndentation) replaceTab2DblSpace(zen);
	if (bConv2CRLF) replace2CRLF(zen);

	dst[0] = 0;
	int zsize = (int)zen.size();
	if (!zsize) {
		return 0;
	} else {
		int begin=0;
		while (begin<zsize && ISCRORLF(zen[begin]))
			begin++;

		int ret=zsize-begin;
		for (int i=0; i<ret; i++)
			dst[i] = zen[i+begin];
		dst[ret] = 0;

		return ret;
	}
}
#ifdef __cplusplus
}
#endif
